1

I'm trying to use an integer to count loop iterations, but after the loop is finished, the variable is blank. I've googled for clues, but no luck.

I have this code:

#!/bin/bash

declare -i count

echo entering loop

while read var
do
  (( count++ ))
  echo $count
done

echo exited loop
echo count is $count

When I run it interactively, pressing Ctrl+D after entering "c", I get output like this:

$ ./example
entering loop
a
1
b
2
c
3
exited loop
count is 3

But when I pipe input into the loop:

#!/bin/bash

declare -i count

echo entering loop

ls -la | while read var
do
  (( count++ ))
  # do something interesting here
  echo $count
done

echo exited loop
echo count is $count

... I get output like this:

$ ./example
entering loop
1
2
3
4
5
exited loop
count is
$

Why doesn't it say "count is 5" here after exiting the loop?

2 Answers2

2

When you pipe values into the while loop, the commands in the pipe are run in a subshell and the value of the counter variable is lost when the while-loop finishes.

You can get around this behaviour by using a process substitution instead of a pipe:

#!/bin/bash

declare -i count
while read var
do
  ((count++))
  echo "$count"
done < <(ls -la)
echo "count is $count"

Or you can enable the lastpipe feature in bash to run the last command of the pipe in the current shell context:

https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html

lastpipe
If set, and job control is not active, the shell runs the last command of a pipeline not executed in the background in the current shell environment.

#!/bin/bash

declare -i count
shopt -s lastpipe # enable lastpipe
ls -la | while read var
do
  ((count++))
  echo "$count"
done
shopt -u lastpipe # disable lastpipe
echo "count is $count"

Related:

Freddy
  • 25,565
1

See §3.2.2 of the BASH Manual: https://www.gnu.org/software/bash/manual/bash.html

Since each pipe is executed in a separate subshell the update of the count variable within the pipe command is out of scope.

However you are able to set the execution of the final pipe to occur within the primary scope using shopt -s lastpipe at the beginning of your script. This will bring the count variable updates into the same scope as the primary shell of your script enabling recovery of the final counter value.

user375693
  • 11
  • 1