A leitura do descritor de arquivo falha

4

Esta questão é sobre leitura e escrita em um descritor de arquivo. Veja o seguinte exemplo:

#!/bin/sh

file='somefile'

# open fd 3 rw
exec 3<> "$file"

# write something to fd 3
printf "%s\n%s\n" "foo" "bar" >&3

# reading from fd 3 works
cat "/proc/$$/fd/3"

# only works if the printf line is removed
cat <&3

exit 0

Este script gera:

foo
bar

Resultado esperado:

foo
bar
foo
bar

Abrir e gravar no descritor de arquivo é bem-sucedido. Então faz a leitura via proc/$$/fd/3 . Mas este não é portátil . cat <&3 não produz nada. No entanto, ele funciona quando o descritor de arquivo não está sendo gravado (por exemplo, descomente a linha printf ).

Por que o cat <&3 não funciona e como ler todo o conteúdo de um descritor de arquivo de forma portável (shell POSIX)?

    
por Marco 04.02.2015 / 21:19

5 respostas

5

cat <&3 faz exatamente o que deve fazer, ou seja, ler o arquivo até chegar ao final do arquivo. Quando você o chama, a posição do arquivo no descritor de arquivo é onde você o deixou, ou seja, no final do arquivo. (Há uma única posição de arquivo, não separada para leitura e escrita).

cat /proc/$$/fd/3 não faz o mesmo que cat <&3 : abre o mesmo arquivo em um descritor diferente. Como cada descritor de arquivo tem sua própria posição e a posição é definida como 0 ao abrir um arquivo para leitura, esse comando imprime o arquivo inteiro e não afeta o script.

Se você quiser ler o que escreveu, será necessário reabrir o arquivo ou retroceder o descritor de arquivo (ou seja, defina sua posição como 0). Não existe uma maneira interna de fazer uma shell POSIX nem a maioria das implementações sh ( existe uma em ksh93 ). Existe apenas um utilitário que pode ser procurado: dd , mas ele só pode procurar. (Existem outros utilitários que podem pular para frente, mas isso não ajuda.)

Acho que a única solução portátil é lembrar o nome do arquivo e abri-lo quantas vezes forem necessárias. Observe que, se o arquivo não for um arquivo comum, talvez você não consiga recuar de qualquer maneira.

    
por 05.02.2015 / 02:11
5
#!/bin/sh

exec 3>file
exec 4<file

printf "%s\n" "foo" "bar" >&3
cat <&4

Usando descritores de arquivo separados para leitura e gravação, você obtém posições separadas no arquivo. Escrever não altera a posição de leitura.

    
por 04.02.2015 / 22:59
3

Com o ksh93, é possível procurar:

#!/usr/bin/env ksh93
file='somefile'
exec 3<> "$file"
printf "%s\n%s\n" "foo" "bar" >&3
cat "/proc/$$/fd/3"
exec 3>#((0))
cat <&3
exit 0

Eu recebo:

foo
bar
foo
bar

como desejado.

    
por 04.02.2015 / 23:12
2

Você precisa da função lseek para reposicionar o deslocamento do arquivo. Ele não está implementado em bash , então você precisa escrever um programa básico em C para isso. Um exemplo disso, e algumas discussões podem ser encontradas na lista de discussão do bash :

int main(int argc, char * argv[])
{
    return lseek(atoi(argv[1]), 0L, 0);
}

Claro que, em um cenário tão simples como em questão, você pode sempre reinicializar o descritor

exec 3<> file

após printf , mas obviamente não é uma solução geral.

    
por 05.02.2015 / 00:23
0

Se você está fazendo algum trabalho em um descritor de arquivo que você espera ler novamente, então você pode portá-lo no corpo de um documento aqui:

exec 4<somefile 3<<plus
$(    printf %s\n some random lines
        cat <&4
)
plus
cat <&3

A solução talvez não seja perfeita - não há uma maneira portátil, por exemplo, de buscar o descritor de arquivo do documento aqui. E a substituição do comando elidirá as linhas em branco do que for que cat grava no stdout, mas todos esses problemas são tratados de maneira muito simples.

Em primeiro lugar, os redirecionamentos são direcionados para o comando composto contendo, e, portanto, é uma pequena questão aninhá-los em um loop para manipular qualquer repetição que você possa exigir. Também é possível implementar um loop dentro do próprio sub-comando - ou mesmo chamar um shell interativo em /dev/tty se você quiser. Mas geralmente prefiro vincular um heredoc a uma definição de função.

fn() { : do something w/ fd3
} 3<<INPUT
$1
$(:gen some output;:maybe cat or head <stdin)
INPUT
while IFS= read -r line; do fn "$line"; done

Também é possível ler um heredoc noutras gravações.

cat 4<<SAVED <<AND
$(  cat)
SAVED
$(  printf %s\n some random lines
     cat <&4)
AND

Ou você poderia fazer um loop como ...

until test && cat <&4
do exec 3<&4 4<<IN
$(: gen output; cat <&3)
IN
done 4<infile

E, claro, você pode chamar algo como fn acima de dentro também.

E em relação ao problema da linha em branco à direita - se é um problema - então você pode simplesmente echo . no final do comando sub, então certifique-se de remover a última linha do comando fd quando você lê como sed \$d <&"$fd" .

Mas, apesar de ser bastante seguro, o loop de arquivos como este no shell geralmente é uma má idéia - note que há pelo menos um fork por arquivo gerado? Shells atribuem fds e utilitários para lidar com eles - faça o loop em um script sed ou awk e manipule os dados do arquivo com um utilitário padrão e use o shell para direcioná-lo na saída - que geralmente é a melhor prática.

    
por 05.02.2015 / 05:00