bash: a variável perde valor no final do loop de leitura

32

Eu tenho um problema em um dos meus scripts de shell. Perguntou a alguns colegas, mas todos eles apenas balançam a cabeça (depois de alguns arranhões), então eu vim aqui para uma resposta.

De acordo com o meu entendimento, o script de shell a seguir deve imprimir "Contagem é 5" como a última linha. Exceto que isso não acontece. Imprime "Contagem é 0". Se o "while read" for substituído por qualquer outro tipo de loop, funcionará bem. Aqui está o script:

echo "1">input.data
echo "2">>input.data
echo "3">>input.data
echo "4">>input.data
echo "5">>input.data

CNT=0 

cat input.data | while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT"

Por que isso acontece e como posso evitá-lo? Eu tentei isso no Debian Lenny e Squeeze, mesmo resultado (ou seja, bash 3.2.39 e bash 4.1.5. Eu admito totalmente não ser um assistente de script de shell, então qualquer ponteiro seria apreciado.

    
por wolfgangsz 13.04.2011 / 19:07

4 respostas

28

Veja o argumento @ Bash FAQ entry # 24: "Eu defino variáveis em um loop. Por que elas desaparecem subitamente após o término do loop? Ou, por que não posso enviar dados para ler? " (mais recentemente arquivado aqui ).

Resumo: Isso é suportado apenas a partir do bash 4.2 e superior. Você precisa usar maneiras diferentes, como substituições de comandos, em vez de um pipe, se estiver usando o bash.

    
por 13.04.2011 / 19:08
29

Isso é um erro "comum". Os pipes criam SubShells, portanto, o while read está sendo executado em um shell diferente do seu script, o que faz com que sua variável CNT nunca seja alterada (somente aquela dentro da subpasta de pipe).

Agrupe o último echo com a subshell while para consertá-lo (há muitas outras maneiras de consertá-lo, essa é uma. As respostas de Iain e Ignacio têm outras.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Longa explicação:

  1. Você declara CNT no seu script como valor 0;
  2. Um SubShell é iniciado no | to while read ;
  3. Sua variável $CNT é exportada para o SubShell com valor 0;
  4. O SubShell conta e aumenta o valor de CNT para 5;
  5. SubShell termina, variáveis e valores são destruídos (eles não retornam ao processo / script de chamada).
  6. Você echo do seu valor CNT original de 0.
por 13.04.2011 / 19:09
8

Isso funciona

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"
    
por 13.04.2011 / 19:11
4

Tente passar os dados em um sub-shell, como se fosse um arquivo antes do loop while. Isso é semelhante à solução de lain, mas assume que você não quer um arquivo intermitente:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
    
por 31.08.2015 / 21:01