unset myVariable i;
while [ -z "$i" ]; do
    read myVariable;
    echo "myVariable : '$myVariable'";
echo "myVariable : '$myVariable'"

(the unset is there to allow replaying the command)

press any key + ENTER, you'll get :

myVariable : '[what you typed]'
myVariable : '[what you typed]'

The value of myVariable exists outside of the while loop. Now try :

echo -e 'foo\nbar\nbaz' >> "$tmpFile"
while read myVariable; do
    echo "myVariable : '$myVariable', otherVariable : '$otherVariable'";
done < "$tmpFile";
echo "myVariable : '$myVariable', otherVariable : '$otherVariable'";
rm "$tmpFile"

you'll get :

myVariable : 'foo', otherVariable : 'whatever'
myVariable : 'bar', otherVariable : 'whatever'
myVariable : 'baz', otherVariable : 'whatever'
myVariable : '', otherVariable : 'whatever'

The value of myVariable is lost when leaving the loop.

Why is there a different behaviour ? Is there a scope trick I'm not aware of ?

NB : running GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

3 Answers 3

while read myVariable; do

The value of myVariable is lost when leaving the loop.

No, myVariable has the value it got from the last read. The script reads from the file, until it gets to the position after the last newline. After that, the final read call gets nothing from the file, sets myVariable to the empty string accordingly, and exits with a false value since it didn't see the delimiter (newline). Then the loop ends.

You can get a nonempty value from the final read if there's an incomplete line after the last newline:

$ printf 'abc\ndef\nxxx' | 
    { while read a; do echo "got: $a"; done; echo "end: $a"; } 
got: abc
got: def
end: xxx

Or use while read a || [ "$a" ]; do ... to handle the final line fragment within the loop body.

  • I've seen while read -r line || [[ -n $line ]]; do process "$line"; done to handle the no-trailing-newline situation Aug 21, 2018 at 14:45

Because you're piping into the while loop, a sub shell is created to run the while loop. Now this child process has it's own copy of the environment and can't pass any variables back to its parent (as in any unix process).

In this case, the done < "$tmpFile" redirection forces bash to generate a sub-shell (as for pipe).

Quoted from bash variable scope on Stack Overflow.

  • 1
    there's no pipe Aug 21, 2018 at 14:42
  • @glennjackman You are right, I added a clarification.
    – andcoz
    Aug 21, 2018 at 16:14

Your second example uses a while loop that has a redirected input.

It reads using < "$tmpFile" and many shells create a sub-shell for this case. You could try to run this script with ksh93. ksh93 does not create a sub-shell in this case.

In your specific case, the reason is completely different:

  • in the first example, you read one line from the input

  • in the second example, you read until EOF is reached

The read command reads the input, then splits the input at IFS characters and then assigns words to the variable arguments of read.

If there are more words than variables as parameters to the read command, the last variable gets a concatenation of the rest of the words.

If there are less words than variables, the other variables are assigned an empty value.

Since you hit EOF, you have no word read but one or more variable as argument to read. This causes all variables to get an empty value assigned.

So something happened that you did not expect: EOF causes the while loop to be terminated and you see no echo command from within the while loop but only the final echo command after the while loop in this EOF case.

This final echo now prints the variable content that was cleared from hitting EOF.

  • 1
    "many shells create a sub-shell for [redirection]" -- really? Do you have a reference for this? Neither bash nor dash create a subshell: echo foo > file; echo $$; while read line; do echo "$$ - $line"; done < file Aug 21, 2018 at 14:44
  • This case means the while loop.
    – schily
    Aug 21, 2018 at 16:10
  • My example is bad: the shell substitutes all the variables including $$ before execution. Nevertheless, redirection does not imply subshell, I believe. Aug 21, 2018 at 17:14
  • I checked the script again and added a final explanation. I hope this is a better explanation than in the other answer.
    – schily
    Aug 21, 2018 at 17:56
  • 1
    I know what you mean by "this case". But there's no subshell for a simple redirection. Using dash, compare (i) using redirection strace -f dash -c 'printf "foo\nbar" > file; while read line; do echo "1: $line"; done < file; echo "2: $line"' to (ii) using pipe strace -f dash -c 'printf "foo\nbar" | while read line; do echo "1: $line"; done; echo "2: $line"' Aug 21, 2018 at 19:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .