BASH scripting - WHILE loop variations - Variable scope

Hello dear Reader,

Could you please help me understand why the variable COUNTER does not keep its value outside the while loop as seen in case A but keeps it in case B? The difference is how the while loop gets its input.

Imagine you have file students.csv

# ./students.csv
John, Doe
Alice, Brown
David, Rosendorf

Case A:

SOURCE_FILE="students.csv"
COUNTER=0

cat $SOURCE_FILE | while IFS="," read FIRST LAST
do
    (( COUNTER++ ))
done

echo $COUNTER
# COUNTER = 0

Case B:

SOURCE_FILE="students.csv"
COUNTER=0

while IFS="," read FIRST LAST
do
    (( COUNTER++ ))
done < $SOURCE_FILE

echo $COUNTER
# COUNTER = 3

I think you forgot to use the read command here

Edit: like this
while IFS=“,” read -r FIRST LAST

Thanks for mentioning that.
That, however, does not change the case and the question remains.

I suppose it creates a sub-shell where the COUNTER is not seen so it creates local COUNTER… but why and where can I read more about this?

When I run either version of your script, it runs fine if I do one of two things:

  • Name the file file.sh and execute the script file with bash file.sh.
    OR
  • Name the file file.sh, chmod u+x file.sh, add #! /bin/bash to the top of the scriptand execute the script with./file.sh`.

My guess is you were using sh the_script_file_name and you needed to be using the bash shell.

Different results on my side…

Could you try? :

# names.csv
John, Doe
Alice, Brown
David, Rosendorf
# while_test.sh
#!/bin/bash

COUNTER_A=0
COUNTER_B=0
FILE="names.csv"

cat $FILE | while IFS="," read LINE
do
        (( COUNTER_A++ ))
done
echo "COUNTER_A: $COUNTER_A"

while IFS="," read LINE
do
        (( COUNTER_B++ ))
done < $FILE
echo "COUNTER_B: $COUNTER_B"


I apologize. You are correct, the first one with COUNTER_A will not work because the inner portion of the while loop is running in a sub shell. You can get what you want by wrapping every after the pipe in { and }.

#!/bin/bash

COUNTER_A=0
COUNTER_B=0
FILE="students.csv"

cat $FILE |
{
  while IFS="," read -r LINE
  do
    (( COUNTER_A++ ))
  done
  echo "COUNTER_A:" $COUNTER_A
}

while IFS="," read -r LINE
do
  (( COUNTER_B++ ))
done < $FILE
echo "COUNTER_B: $COUNTER_B"

Also, in case the last line in your file does not have a newline character, you can add the following at the end of while IFS="," read -r LINE to capture the last line of the file.

|| [[ -n "$LINE" ]]