6

Consider:

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"

If I remove | tee logfile the max variable is correctly printed as 211, but if I leave it in I get Max= 0.

What is going on?

MikeD
  • 810
tetris11
  • 355

3 Answers3

6

Each side of a pipe is executed in a subshell. A subshell is copy of the original shell process which starts in the same state and then evolves independently¹. Variables set in the subshell cannot escape back to the parent shell.

In bash, instead of using a pipe, you could use process substitution. This would behave almost identically to the pipe, except that the loop executes in the original shell and only tee executes in a 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"

While I was at it I changed a few things in your script:

Note that there is a small difference between the pipe solution and the subshell solution: a pipe command finishes when both commands exit, whereas a command with a process substitution finishes when the main command exits, without waiting for the process in the process substitution. This means that when your script finishes, the log file may not be fully written.

Another approach is to use some other channel to communicate from the subshell to the original shell process. You can use a temporary file (flexible, but harder to do right — write the temporary file to an appropriate directory, avoid name conflicts (use mktemp), remove it even in case of errors). Or you can use another pipe to communicate the result and grab the result in a command substitution.

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

The output of tee goes to file descriptor 3 which is redirected to the script's standard output. The output of echo "$max" goes to file descriptor 4 which is redirected into the command substitution.

5

Once you put for loop into a pipe, it runs in a subshell which does not pass its variables to supershell.

Tagwint
  • 2,480
2

It doesn't survive the pipe, but this works:

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"  
MikeD
  • 810