Bash: Escopo de variáveis em um loop for usando tee

4

Considere:

numbers="1 111 5 23 56 211 63"
max=0

for num in ${numbers[@]}; do

      [ $num -gt $max ]\
           && echo "old -> new max = $max -> $num"\
           && max=$num

done | tee logfile

echo "Max= $max"

Se eu remover | tee logfile , a variável max será impressa corretamente como 211, mas se eu deixar, eu receberei Max= 0 .

O que está acontecendo?

    
por tetris11 28.02.2017 / 16:48

3 respostas

2

Cada lado de um pipe é executado em um subshell. Um subshell é uma cópia do processo shell original que começa no mesmo estado e depois evolui independentemente¹. As variáveis configuradas no subshell não podem voltar para o shell pai.

No bash, em vez de usar um canal, você pode usar substituição de processo . Isso se comportaria quase identicamente ao pipe, exceto que o loop é executado no shell original e somente tee é executado em um subshell.

for num in "${numbers[@]}"; do
  if ((num > max)); then
    echo "old -> new max = $max -> $num"
    max=$num
  fi
done > >(tee logfile)
echo "Max= $max"

Enquanto eu estava nisso, mudei algumas coisas no seu script:

Note que existe uma pequena diferença entre a solução pipe e a solução subshell: um comando pipe termina quando ambos os comandos saem, enquanto um comando com uma substituição de processo termina quando o comando principal sai, sem esperar pelo processo no processo substituição. Isso significa que quando o script terminar, o arquivo de log pode não estar totalmente escrito.

Outra abordagem é usar algum outro canal para se comunicar do subshell para o processo de shell original. Você pode usar um arquivo temporário (flexível, mas mais difícil de fazer certo - gravar o arquivo temporário em um diretório apropriado, evitar nome conflitos (use mktemp ), remova-o mesmo em caso de erros). Ou você pode usar outro canal para comunicar o resultado e pegue o resultado em uma substituição de comando.

max=$({ { for … done;
          echo "$max" >&4
        } | tee logfile >&3; } 4>&1) 3&>1

A saída de tee vai para o descritor de arquivo 3, que é redirecionado para a saída padrão do script. A saída de echo "$max" vai para o descritor de arquivo 4, que é redirecionado para a substituição do comando.

    
por 02.03.2017 / 01:13
5

Uma vez que você coloca o loop em um pipe, ele é executado em um subshell que não passa suas variáveis para o supershell.

    
por 28.02.2017 / 16:54
2

Ele não sobrevive ao pipe, mas isso funciona:

numbers="1 111 5 23 56 211 63"  
max=0  
echo $max>maxfile  

for num in ${numbers[@]}; do  

      [ $num -gt $max ]\  
           && echo "old -> new max = $max -> $num"\  
           && max=$num\  
           && echo $max>maxfile  

done | tee logfile  

read max<maxfile  
echo "Max= $max"  
    
por 28.02.2017 / 20:56