Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

I have a problem in one of my shell scripts. Asked a few colleagues, but they all just shake their heads (after some scratching), so I've come here for an answer.

According to my understanding the following shell script should print "Count is 5" as the last line. Except it doesn't. It prints "Count is 0". If the "while read" is replaced with any other kind of loop, it works just fine. Here's the script:

echo "1">input.data
echo "2">>input.data
echo "3">>input.data
echo "4">>input.data
echo "5">>input.data

CNT=0 

cat input.data | while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT"

Why does this happen and how can I prevent it? I've tried this in Debian Lenny and Squeeze, same result (i.e. bash 3.2.39 and bash 4.1.5. I fully admit to not being a shell script wizard, so any pointers would be appreciated.

share|improve this question

3 Answers 3

up vote 9 down vote accepted

BASh FAQ entry #24: "I set variables in a loop. Why do they suddenly disappear after the loop terminates? Or, why can't I pipe data to read?"

share|improve this answer
    
You get the bonus, since your answer provided me with the widest range of options. –  wolfgangsz Apr 13 '11 at 17:22

This is kind of a 'common' mistake. Pipes create SubShells, so the while read is running on a different shell than your script, that makes your CNT variable never changes (only the one inside the pipe subshell).

Group the last echo with the subshell while to fix it (there are many other way to fix it, this is one. Iain and Ignacio's answers have others.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Long explanation:

  1. You declare CNT on your script to be value 0;
  2. A SubShell is started on the | to while read;
  3. Your $CNT variable is exported to the SubShell with value 0;
  4. The SubShell counts and increase the CNT value to 5;
  5. SubShell ends, variables and values are destroyed (they don't get back to the calling process/script).
  6. You echo your original CNT value of 0.
share|improve this answer
    
First shell script I ever wrote gave me the same issues, banged my head against the wall for awhile before finding out that that pipes spawn additional shells. Any variable you mess with in a pipe will go out of scope as soon as the pipe ends--meaning that if you really, really want to do something with a variable outside of the pipe in which it was used, you'll have to hold state through something funky like a temporary file. –  photoionized Apr 13 '11 at 17:17
    
Excellent answer, unfortunately I can only give one acceptance bonus. Sorry. –  wolfgangsz Apr 13 '11 at 17:23

This works

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.