Arquivo de substituição do processo de reutilização do Bash

5

Eu tenho um script grande que pega um arquivo como entrada e faz várias coisas com ele. Aqui está uma versão de teste:

echo "cat: $1"
cat $1
echo "grep: $1"
grep hello $1
echo "sed: $1"
sed 's/hello/world/g' $1

Eu quero que meu script funcione com a substituição do processo, mas apenas o primeiro comando ( cat ) funciona, enquanto o resto não funciona. Eu acho que isso é porque é um cachimbo.

$ myscript.sh <(echo hello)

deve imprimir:

cat: /dev/fd/63
hello
grep: /dev/fd/63
hello
sed: /dev/fd/63
world

Isso é possível?

    
por dogbane 10.08.2011 / 10:56

4 respostas

7

A construção <(…) cria um canal. O pipe é passado por meio de um nome de arquivo como /dev/fd/63 , mas esse é um tipo especial de arquivo: abri-lo realmente significa duplicar o descritor de arquivo 63. (Veja o final de esta resposta para mais explicações.)

Ler de um pipe é uma operação destrutiva: uma vez que você pegou um byte, você não pode jogá-lo de volta. Portanto, seu script precisa salvar a saída do pipe. Você pode usar um arquivo temporário (preferível se a entrada for grande) ou uma variável (preferível se a entrada for pequena). Com um arquivo temporário:

tmp=$(mktemp)
cat <"$1" >"$tmp"
cat <"$tmp"
grep hello <"$tmp"
sed 's/hello/world/g' <"$tmp"
rm -f "$tmp"

(Você pode combinar as duas chamadas para cat como tee <"$1" -- "$tmp" .) Com uma variável:

tmp=$(cat)
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'

Observe que a substituição de comandos $(…) trunca todas as novas linhas no final da saída do comando. Para evitar isso, adicione um caractere extra e retire-o depois.

tmp=$(cat; echo a); tmp=${tmp%a}
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'

A propósito, não esqueça as aspas duplas em torno das substituições de variáveis.

    
por 11.08.2011 / 00:41
4

Quando você usa um arquivo, pode ler seus dados muitas vezes. Quando você usa um pipe nomeado (o que é realmente criado pela substituição de processo), só é possível lê-lo uma vez. Portanto, os comandos grep e sed recebem entrada vazia.

( Como entender os tubos pode ser uma boa leitura. )

Para o que você quer fazer com a substituição do processo, você pode escrever algo como:

cat $1 | tee >(echo "cat: $1"; cat) | tee >(echo "grep: $1"; grep hello) | (echo "sed: $1"; sed 's/hello/world/g')

Mas, nesse caso, o segundo cat , grep e sed seria executado em paralelo e sua saída intercalada. Isso pode ser mais útil:

cat $1 | tee >(cat > cat.txt) | tee >(grep hello > grep.txt) | sed 's/hello/world/g' > sed.txt
    
por 10.08.2011 / 15:44
2

A maneira usual de fazer isso é tornar o parâmetro $1 opcional. Então, pode-se definir FILE=${1-/dev/stdin} e usar FILE várias vezes. No entanto, ler várias vezes em um pipe será lido sequencialmente, os dados não serão duplicados.

A solução mais fácil para esse problema seria usar algum arquivo temporário.

if [ -z "$1" ] ; then FILE=$(mktemp); cat >FILE; else FILE=$1; fi

Se você deseja passar explicitamente algum nome de arquivo (eventualmente /dev/fd/x ), o mesmo truque de arquivo temporário pode ser usado:

FILE=$(mktemp); cat "$1" >FILE

Você também pode fazer uso complexo de tee para duplicar a entrada de stdin filedescriptor para vários outros filodescriptors. Mas este último método seria bastante pesado.

    
por 10.08.2011 / 11:27
0

O arquivo obtido por uma substituição de processo não é pesquisável, dependendo da implementação subjacente, portanto, você não pode lê-lo mais de uma vez.

    
por 10.08.2011 / 11:17