Por que minha variável local está em um loop 'while read', mas não em outro loop aparentemente similar?

19

Por que recebo valores diferentes para $x dos 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?
    
por Peter.O 23.03.2011 / 15:24

4 respostas

23

A explicação correta já foi dada por jsbillings e geekosaur , mas deixe-me expandir um pouco sobre isso.

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 variáveis de configuração) permanece confinada a esse segmento de um pipeline. A única informação que você pode obter de um subshell é o que ele produz (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, as seguintes impressões de fragmentos 0:

a=0; a=1 | a=2; echo $a

Em ksh (as variantes derivadas do código AT & T, não variantes pdksh / mksh) e zsh, o último item em um pipeline é executado no shell pai. (POSIX permite ambos os comportamentos.) Então o trecho acima imprime 2.

Um idioma útil é incluir a continuação do loop while (ou qualquer coisa que você tenha no lado direito do pipeline, mas um loop while é comum aqui) no pipeline:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}
    
por 23.03.2011 / 22:21
9

Você está executando um problema de escopo variável. As variáveis definidas no loop while que estão no lado direito do pipe 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 COPY do ambiente shell, e quaisquer alterações no ambiente são perdidas no final do shell. Veja esta pergunta do StackOverflow .

ATUALIZADO : Eu negligenciei o fato importante de que o loop while com seu próprio subshell era devido a ser o ponto final de um pipe, eu atualizei isso na resposta.

    
por 23.03.2011 / 15:33
5

Como mencionado em outras respostas , as partes de um pipeline são executadas em subshells, então as modificações feitas não são ' t visível para o shell principal.

Se considerarmos apenas o Bash, há duas outras soluções alternativas além da estrutura cmd | { stuff; more stuff; } :

  1. Redirecionar a entrada da substituição do processo :

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"
    

    A saída do comando em <(...) é feita para aparecer como se fosse um pipe nomeado.

  2. A opção lastpipe , que faz o Bash funcionar como o ksh, e executa a última parte do pipeline no processo principal do shell. Embora funcione apenas se o controle de trabalho estiver desativado, ou seja, não em um shell interativo:

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

    ou

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

A substituição do processo é obviamente suportada em ksh e zsh também. Mas já que eles executam a última parte do pipeline no shell principal de qualquer maneira, usá-lo como uma solução alternativa não é realmente necessário.

    
por 29.11.2017 / 19:55
0
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

pode funcionar.

    
por 03.05.2016 / 08:47