Por que obtenho valores diferentes para $x
os snippets abaixo?
#!/bin/bash
x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x
# x=55 .. I'd expect this result
x=1
cat junk | while read var ; do x=55 ; done
echo x=$x
# x=1 .. but why?
x=1
echo fred | while read var ; do x=55 ; done
echo x=$x
# x=1 .. but why?
Respostas:
A explicação correta já foi dada por jsbillings e geekosaur , mas deixe-me expandir um pouco.
Na maioria dos shells, incluindo o bash, cada lado de um pipeline é executado em um subshell, portanto, qualquer alteração no estado interno do shell (como definir variáveis) permanece confinada ao segmento de um pipeline. A única informação que você pode obter de um subshell é o que ele gera (para saída padrão e outros descritores de arquivo) e seu código de saída (que é um número entre 0 e 255). Por exemplo, o seguinte snippet imprime 0:
No ksh (as variantes derivadas do código AT&T, não nas variantes pdksh / mksh) e no zsh, o último item de um pipeline é executado no shell pai. (O POSIX permite os dois comportamentos.) Portanto, o trecho acima imprime 2.
Um idioma útil é incluir a continuação do loop while (ou o que você tiver no lado direito do pipeline, mas um loop while é realmente comum aqui) no pipeline:
fonte
< <(locate -ber ^\.tag$)
, graças à resposta pouco clara original e aos avisos de geekosaur e glenn jackman .. Eu estava inicialmente em um dilema sobre aceitar a resposta, mas o resultado foi foi muito claro, especialmente com jsbillings acompanhamento comentário :)Você está enfrentando um problema de escopo variável. As variáveis definidas no loop while, que estão no lado direito do canal, têm seu próprio contexto de escopo local e as alterações na variável não serão vistas fora do loop. O loop while é essencialmente um subshell que obtém uma cópia do ambiente do shell, e quaisquer alterações no ambiente são perdidas no final do shell. Veja esta pergunta StackOverflow .
ATUALIZADO : Esqueci de salientar o fato importante de que o loop while com seu próprio subshell se deve ao fato de ser o ponto final de um canal, atualizei isso na resposta.
fonte
while
loop como o final de um pipeline que o lança em um subshell.blah|blah|while read ...
, você pode terwhile read ...; done < <(blah|blah)
Como mencionado em outras respostas , as partes de um pipeline são executadas em subcascas, portanto as modificações feitas lá não são visíveis para o shell principal.
Se considerarmos apenas o Bash, há duas outras soluções alternativas além da
cmd | { stuff; more stuff; }
estrutura:Redirecione a entrada da substituição do processo :
A saída do comando in
<(...)
é feita para parecer como se fosse um pipe nomeado.A
lastpipe
opção, que faz o Bash funcionar como ksh, e executa a última parte do pipeline no processo principal do shell. Embora só funcione se o controle de trabalho estiver desabilitado, ou seja, não em um shell interativo:ou
É claro que a substituição de processos também é suportada no ksh e no zsh. Mas como eles executam a última parte do pipeline no shell principal de qualquer maneira, usá-lo como solução alternativa não é realmente necessário.
fonte
isso pode funcionar.
fonte