In the following program, if I set the variable $foo
to the value 1 inside the first if
statement, it works in the sense that its value is remembered after the if statement. However, when I set the same variable to the value 2 inside an if
which is inside a while
statement, it's forgotten after the while
loop. It's behaving like I'm using some sort of copy of the variable $foo
inside the while
loop and I am modifying only that particular copy. Here's a complete test program:
#!/bin/bash
set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting \$foo to 1: $foo"
fi
echo "Variable \$foo after if statement: $foo"
lines="first line\nsecond line\nthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable \$foo updated to $foo inside if inside while loop"
fi
echo "Value of \$foo in while loop body: $foo"
done
echo "Variable \$foo after while loop: $foo"
# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1
# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)
How about a very simple method
You are the 742342nd user to ask this bash FAQ. The answer also describes the general case of variables set in subshells created by pipes:
Hmmm... I would almost swear that this worked for the original Bourne shell, but don't have access to a running copy just now to check.
There is, however, a very trivial workaround to the problem.
Change the first line of the script from:
to
Et voila! A read at the end of a pipeline works just fine, assuming you have the Korn shell installed.
The
while
loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.Instead you can use a here string to re-write the while loop to be in the main shell process; only
echo -e $lines
will run in a subshell:UPDATED#2
Explanation is in Blue Moons's answer.
Alternative solutions:
Eliminate
echo
Add the echo inside the here-is-the-document
Run
echo
in background:Redirect to a file handle explicitly (Mind the space in
< <
!):Or just redirect to the
stdin
:And one for
chepner
(eliminatingecho
):Variable
$lines
can be converted to an array without starting a new sub-shell. The characters\
andn
has to be converted to some character (e.g. a real new line character) and use the IFS (Internal Field Separator) variable to split the string into array elements. This can be done like:Result is
This is an interesting question and touch a very basic concept in Bourne shell and subshell. Here I provide a solution that is different from the previous solutions by doing some kind of filtering. I will give an example that may be useful in real life. This is a fragment for checking the downloaded files conform to know checksums. The checksum file look like the following (Showing just 3 lines):
The shell script:
The parent and subshell communicate through the echo command. You can pick some easy to parse text for the parent shell. This method does not break your normal way of thinking, just that you have to do some post processing. You can use grep, sed, awk, and more for doing so.