Why does my Bash counter reset after while loop

I have a Bash script where I want to count how many things were done when looping through a file. The count seems to work within the loop but after it the variable seems reset.

nKeys=0
cat afile | while read -r line
do
  #...do stuff
  let nKeys=nKeys+1
  # this will print 1,2,..., etc as expected
  echo Done entry $nKeys
done
# PROBLEM: this always prints "... 0 keys"
echo Finished writing $destFile, $nKeys keys

The output of the above is something alone the lines of:

Done entry 1
Done entry 2
Finished writing /blah, 0 keys

The output I want is:

Done entry 1
Done entry 2
Finished writing /blah, 2 keys

I am not quite sure why nKeys is 0 after the loop :( I assume it's something basic but damned if I can spot it despite looking at http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-7.html and other resources.

Fingers crossed someone else can look at it and go "well duh! You have to ..."!


Solution 1:

In the just-released Bash 4.2, you can do this to prevent creating a subshell:

shopt -s lastpipe

Also, as you'll probably see at the link Ignacio provided, you have a Useless Use of cat.

while read -r line
do
    ...
done < afile

Solution 2:

As mentioned in the accepted answer, this happens because pipes spawn separate subprocesses. To avoid this, command grouping has been the best option for me. That is, doing everything after the pipe in a subshell.

nKeys=0
cat afile | 
{
  while read -r line
  do
    #...do stuff
    let nKeys=nKeys+1
    # this will print 1,2,..., etc as expected
    echo Done entry $nKeys
  done
  # PROBLEM: this always prints "... 0 keys"
  echo Finished writing $destFile, $nKeys keys
}

Now it will report the value of $nKeys "correctly" (i.e. what you wish).