JMW
JMW

Reputation: 7807

Why is a variable changed within a loop unchanged outside the loop

I use command | awk '{ print $1; }' | while read val ; do to loop through the command output. Recently I wanted to calculate the sum, and I discovered some strange behaviour in bash:

content of test.txt

100
200
300

content of test.sh

sum='0'
cat test.txt | awk '{ print $1; }' | while read val ; do
        sum=`expr $sum + $val`
        echo "sum is now: $sum"
done

echo "FINAL SUM: $sum"

output from executing test.sh

sum is now: 100
sum is now: 300
sum is now: 600
FINAL SUM: 0

the final sum should be 600. What can I do to fix this?

Upvotes: 1

Views: 2818

Answers (4)

doubleDown
doubleDown

Reputation: 8398

This strange behaviour is actually caused by the bash pipes

To quote the Bash reference manual

Each command in a pipeline is executed in its own subshell

Think of sum as a local variable in your while loop, that's why sum appears not to be set when you go out of the while-loop.

The solutions proposed by the others will work fine.

Upvotes: 0

imp25
imp25

Reputation: 2357

To expand on what newfurniturey states, but in a way where you can use it with an arbritary input command, not just cat:

sum='0' 
while read val ; do
     sum=`expr $sum + $val`
     echo "sum is now: $sum" 
done < <(cat test.txt | awk '{ print $1 }')
echo "FINAL SUM: $sum"

Replace cat test.txt with whatever command which outputs the input you require.

Upvotes: 2

newfurniturey
newfurniturey

Reputation: 38416

The cause is the use of cat, which spawns another subshell. This means that the sum variable is incremented in the second subshell, and then goes out of scope (and returns to it's previous value of 0) when the loop is finished.

Try updating your loop to not use cat:

sum='0'
while read val ; do
        sum=`expr $sum + $val`
        echo "sum is now: $sum"
done < test.txt

echo "FINAL SUM: $sum"

If you don't actually need the loop (i.e. - if you're not handling any other column/content-processing), you could use awk directly and store it's value into the sum variable:

sum=`awk '{ sum += $1; } END { print sum }' test.txt`;

Upvotes: 2

Steve
Steve

Reputation: 54392

There's no need for the bash pipe. You can do it all with awk:

awk '{sum+= $1; printf "The sum is now: %s\n", sum } END { print "FINAL SUM:", sum }' file.txt

Results:

The sum is now: 100
The sum is now: 300
The sum is now: 600
FINAL SUM: 600

Upvotes: 2

Related Questions