Ler valores em uma variável de shell de um pipe

Eu estou tentando obter bash para processar dados de stdin que é canalizado, mas sem sorte. O que quero dizer é nenhum dos seguintes trabalhos:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test test= echo "hello world" | read test; echo test=$test test= echo "hello world" | test=`cat`; echo test=$test test= 

onde eu quero que a saída seja test=hello world . Eu tentei colocar “” aspas em torno do "$test" que também não funciona.

Usar

 IFS= read var < < EOF $(foo) EOF 

Você pode enganar read em aceitar de um tubo como este:

 echo "hello world" | { read test; echo test=$test; } 

ou até mesmo escrever uma function como esta:

 read_from_pipe() { read "$@" < &0; } 

Mas não faz sentido - suas atribuições variables ​​podem não durar! Um pipeline pode gerar um subshell, onde o ambiente é herdado por valor, não por referência. É por isso que a read não se incomoda com a input de um cano - ela é indefinida.

FYI, http://www.etalabs.net/sh_tricks.html é uma coleção bacana do lixo necessário para combater as esquisitices e incompatibilidades de conchas de bourne, sh.

Se você quiser ler muitos dados e trabalhar em cada linha separadamente, você pode usar algo assim:

 cat myFile | while read x ; do echo $x ; done 

Se você quiser dividir as linhas em várias palavras, você pode usar várias variables ​​no lugar de x assim:

 cat myFile | while read xy ; do echo $y $x ; done 

alternativamente:

 while read xy ; do echo $y $x ; done < myFile 

Mas assim que você começar a querer fazer algo realmente inteligente com esse tipo de coisa, é melhor usar alguma linguagem de script como o perl, onde você poderia tentar algo assim:

 perl -ane 'print "$F[0]\n"' < myFile 

Há uma curva de aprendizado bastante íngreme com perl (ou eu acho que qualquer um desses idiomas), mas você vai achar muito mais fácil a longo prazo, se você quiser fazer qualquer coisa, mas o mais simples dos scripts. Eu recomendaria o Perl Cookbook e, claro, The Perl Programming Language por Larry Wall et al.

Esta é outra opção

 $ read test < <(echo hello world) $ echo $test hello world 

read não lê de um pipe (ou possivelmente o resultado é perdido porque o pipe cria um subshell). Você pode, no entanto, usar uma string aqui no Bash:

 $ read abc < << $(echo 1 2 3) $ echo $a $b $c 1 2 3 

Não sou especialista em Bash, mas me pergunto por que isso não foi proposto:

 stdin=$(cat) echo "$stdin" 

Prova de uma linha que funciona para mim:

 $ fortune | eval 'stdin=$(cat); echo "$stdin"' 

bash 4.2 introduz a opção lastpipe , que permite que seu código funcione como escrito, executando o último comando em um pipeline no shell atual, em vez de um subshell.

 shopt -s lastpipe echo "hello world" | read test; echo test=$test 

A syntax de um pipe implícito de um comando shell para uma variável bash é

 var=$(command) 

ou

 var=`command` 

Nos seus exemplos, você está canalizando dados para uma instrução de atribuição, que não espera nenhuma input.

Aos meus olhos, a melhor maneira de ler stdin no bash é a seguinte, que também permite trabalhar nas linhas antes que a input termine:

 while read LINE; do echo $LINE done < /dev/stdin 

A primeira tentativa foi bem próxima. Essa variação deve funcionar:

 echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; }; 

e a saída é:

teste = olá mundo

Você precisa de chaves após o pipe para include a tarefa a ser testada e o eco.

Sem as chaves, a tarefa a ser testada (depois do pipe) está em um shell, e o echo "test = $ test" está em um shell separado que não sabe sobre essa atribuição. É por isso que você estava obtendo "test =" na saída, em vez de "test = hello world".

Piping algo em uma expressão envolvendo uma atribuição não se comporta assim.

Em vez disso, tente:

 test=$(echo "hello world"); echo test=$test 

O seguinte código:

 echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test ) 

vai funcionar também, mas vai abrir outra nova sub-shell depois do pipe, onde

 echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; } 

não vai.


Eu tive que desativar o controle de trabalho para fazer uso do método chepnars (eu estava executando este comando do terminal):

 set +m;shopt -s lastpipe echo "hello world" | read test; echo test=$test echo "hello world" | test="$( 

Bash Manual diz :

lastpipe

Se definido e o controle de tarefa não estiver ativo , o shell executará o último comando de um pipeline não executado em segundo plano no ambiente atual do shell.

Nota: o controle de tarefa é desativado por padrão em um shell não interativo e, portanto, você não precisa do set +m dentro de um script.

Porque eu me apaixono por isso, eu gostaria de soltar uma nota. Eu encontrei este segmento, porque eu tenho que rewrite um script sh antigo para ser compatível com POSIX. Isso basicamente significa contornar o problema pipe / subshell introduzido pelo POSIX reescrevendo o código da seguinte forma:

 some_command | read abc 

para dentro:

 read abc < < EOF $(some_command) EOF 

E código assim:

 some_command | while read abc; do # something done 

para dentro:

 while read abc; do # something done < < EOF $(some_command) EOF 

Mas o último não se comporta da mesma forma na input vazia. Com a notação antiga, o loop while não é inserido na input vazia, mas na notação POSIX é! Acho que é devido à nova linha antes do EOF, que não pode ser omitido. O código POSIX que se comporta mais como a notação antiga se parece com isso:

 while read abc; do case $a in ("") break; esac # something done < < EOF $(some_command) EOF 

Na maioria dos casos, isso deve ser bom o suficiente. Mas infelizmente isso ainda não se comporta exatamente como a velha anotação se algum comando imprime uma linha vazia. Na antiga notação, o corpo while é executado e, na notação POSIX, nós quebramos na frente do corpo.

Uma abordagem para corrigir isso pode ser assim:

 while read abc; do case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac # something done < < EOF $(some_command) echo "something_guaranteed_not_to_be_printed_by_some_command" EOF 

Eu acho que você estava tentando escrever um script de shell que poderia ter input de stdin. mas enquanto você está tentando fazer isso inline, você se perdeu tentando criar aquela variável test =. Eu acho que não faz muito sentido fazer isso em linha, e é por isso que não funciona da maneira que você espera.

Eu estava tentando reduzir

 $( ... | head -n $X | tail -n 1 ) 

para obter uma linha específica de várias inputs. então eu poderia digitar …

 cat program_file.c | line 34 

então eu preciso de um pequeno programa shell capaz de ler stdin. como você faz.

 22:14 ~ $ cat ~/bin/line #!/bin/sh if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi cat | head -n $1 | tail -n 1 22:16 ~ $ 

ai está.

Que tal agora:

 echo "hello world" | echo test=$(cat)