Variable manipulation from within a Bash subshell

Consider a shell script that counts the number of lines in a file. There are a number of ways to do this and I initially chose the following method:

NUMLINES=0
cat /tmp/file.txt | while read LINE;
do
NUMLINES=`expr $NUMLINES + 1`
done
echo "Number of lines was $NUMLINES"

However, using this approach the value of NUMLINES was always zero after the while loop has ended, even though when I printed out its value inside the while loop it appeared to be incrementing just fine.

The problem turned out to be the use of the pipe at the beginning of the the while loop. This causes a new sub-shell to be created, which in turn creates a new local version of the NUMLINES. Thus, the original copy of the NUMLINES variable (outside the while loop) is never actually changed. Here is one solution to this that eliminates the use of the pipe (i.e. replace the piping of the file in the while with a redirection from the file at the end of the while statement):

NUMLINES=0
while read LINE;
do
NUMLINES=`expr $NUMLINES + 1`
done < /tmp/file.txt
echo "Number of lines was $NUMLINES"

This worked for me!


6 thoughts on “Variable manipulation from within a Bash subshell”

  1. I should have been more careful with my choice of sample context for try to explain this issue. Clearly, “wc -l” is the easiest and simplest way to count the number of lines in a file.

    The issue this post is attempting to highlight is more generally to do with variable manipulation inside loops. The real context in which I discovered this has nothing at all to do with counting the number of lines in a file but rather counting how many times around a certain loop I hit a certain condition, and then reporting this afterwards.

  2. I also discovered a rather neat alternative to the way in which you increment variables in bash. The VAR=`expr $VAR +1` shown above is one of more the classic/common ways to do this.

    However, bash supported C-style variable manipulation using the (( )) operator as follows:

    ((VAR++)) will increment VAR
    ((VAR+=34)) will add 34 to the value of VAR

    Very useful indeed!

  3. Thank you. I’ve been struggling for a couple of hours trying to find a way to address a very similar problem. This gave me the insight I needed to craft a solution.

  4. The solution gets trickier, if you do not simply want to read the lines from a file as in OP’s post. Consider this for example:

    find /tmp | while read LINE; do
    # do something with the list of files in /tmp
    done

    Here is my (more versatile) solution:

    NUMLINES=0
    # capture loop input into variable
    LINES=$(find /tmp)
    # redirect variable into loop using <<EOF
    while read LINE; do
    NUMLINES=`expr $NUMLINES + 1`
    done <<EOF
    $LINES
    EOF
    echo "Number of lines was $NUMLINES"

Leave a Reply