Pergunta

Tentei usar o descritor de arquivos de leitura/gravação em Bash para poder excluir o arquivo ao qual o descritor de arquivo se referiu depois, como tal:

F=$(mktemp)
exec 3<> "$F"
rm -f "$F"

echo "Hello world" >&3
cat <&3

mas o cat O comando não fornece saída. Posso conseguir o que quero se usar descritores de arquivos separados para ler e escrever:

F=$(mktemp)
exec 3> "$F"
exec 4< "$F"
rm -f "$F"

echo "Hello world" >&3
cat <&4

que imprime Hello world.

Suspeitei que Bash não procurasse automaticamente o início do descritor de arquivos quando você muda de escrever para lê -lo, e a seguinte combinação de código Bash e Python confirma isso:

fdrw.sh

exec 3<> tmp
rm tmp

echo "Hello world" >&3
exec python fdrw.py

fdrw.py

import os  

f = os.fdopen(3)
print f.tell()
print f.read()

que dá:

$ bash fdrw.sh
12

$ # This is the prompt reappearing

Existe uma maneira de alcançar o que eu quero apenas usar o Bash?

Foi útil?

Solução

Não. Bash não tem nenhum conceito de "busca" com seu redirecionamento. Ele lê/grava (principalmente) do começo ao fim em um longo fluxo.

Outras dicas

Se você deseja procurar descritores de arquivos Bash, poderá usar um subprocesso, pois herda os descritores de arquivos do processo pai. Aqui está um programa do Exemplo C para fazer isso.

Seekfd.c

#define _FILE_OFFSET_BITS 64
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    /* Arguments: fd [offset [whence]]
     * where
     * fd: file descriptor to seek
     * offset: number of bytes from position specified in whence
     * whence: one of
     *  SEEK_SET (==0): from start of file
     *  SEEK_CUR (==1): from current position
     *  SEEK_END (==2): from end of file
     */
    int fd;
    long long scan_offset = 0;
    off_t offset = 0;
    int whence = SEEK_SET;
    int errsv; int rv;
    if (argc == 1) {
        fprintf(stderr, "usage: seekfd fd [offset [whence]]\n");
        exit(1);
    }
    if (argc >= 2) {
        if (sscanf(argv[1], "%d", &fd) == EOF) {
            errsv = errno;
            fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
            exit(1);
        }
    }
    if (argc >= 3) {
        rv = sscanf(argv[2], "%lld", &scan_offset);
        if (rv == EOF) {
            errsv = errno;
            fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
            exit(1);
        }
        offset = (off_t) scan_offset;
    }
    if (argc >= 4) {
        if (sscanf(argv[3], "%d", &whence) == EOF) {
            errsv = errno;
            fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
            exit(1);
        }
    }

    if (lseek(fd, offset, whence) == (off_t) -1) {
        errsv = errno;
        fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
        exit(2);
    }

    return 0;
}

Encontrei uma maneira de fazer isso em Bash, mas está dependendo de uma característica obscura de exec < /dev/stdin que na verdade pode rebobinar o descritor de arquivo de stdin de acordo com http://linux-ip.net/misc/madlug/shell-tips/tip-1.txt:

F=$(mktemp)
exec 3<> "$F"
rm -f "$F"

echo "Hello world" >&3
{ exec < /dev/stdin; cat; } <&3

O descritor de gravação não é afetado por isso, então você ainda pode anexar a saída ao descritor 3 antes do gato.

Infelizmente, só consegui isso funcionando com o Linux não no MacOS (BSD), mesmo com a mais nova versão Bash. Portanto, não parece muito portátil.

Tente alterar a sequência de comandos:

F=$(mktemp tmp.XXXXXX)
exec 3<> "$F"
echo "Hello world" > "$F"
rm -f "$F"

#echo "Hello world" >&3
cat <&3

Quando você abre um descritor de arquivo em Bash como esse, ele se torna acessível como um arquivo em /dev/fd/. Nisso você pode fazer cat e vai ler desde o início ou anexar (echo "something" >> /dev/fd/3), e ele adicionará ao final. Pelo menos no meu sistema, ele se comporta dessa maneira. (Por outro lado, não consigo conseguir fazer com que "Cat <& 3" funcione, mesmo que eu não escreva o descritor).

#!/bin/bash
F=$(mktemp tmp.XXXXXX)
exec 3<> $F
rm $F

echo "Hello world" >&3
cat /dev/fd/3

Como sugerido em outra resposta, cat Reparará o descritor de arquivo antes de ler, pois acha que é apenas um arquivo regular.

Para 'rebobinar' o descritor de arquivo, você pode simplesmente usar /proc/self/fd/3

Script de teste:

#!/bin/bash

# Fill data
FILE=test
date +%FT%T >$FILE

# Open the file descriptor and delete the file
exec 5<>$FILE
rm -rf $FILE

# Check state of the file
# should return an error as the file has been deleted
file $FILE

# Check that you still can do multiple reads or additions
for i in {0..5}; do
    echo ----- $i -----

    echo . >>/proc/self/fd/5
    cat /proc/self/fd/5

    echo
    sleep 1
done

Tente matar -9 o script enquanto estiver em execução, você verá que, ao contrário do que acontece com o método do TRAP, o arquivo é realmente excluído.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top