while loops and variables under bash

Hi,

This is probably going to be very simple but i came across something i can't quite explain. Here is the situation: i have a list of files, which i'd like to process one by one (get the size, make some tests, whatever) and generate some statistics using different variables.

Something similar to this:

u=0
ls|while read f; do
#until [ $u -gt 5 ]; do
#for f in `ls `; do
echo inside u=$u
let "u+=1"
done

echo outside u=$u

And here are the results (there are 3 files in the current directory):

So basically the variable u appears to get re-initialised somehow.

Using until or a classic for loop u keeps its value after the loop is executed, like this:

So what's the deal ? That's under bash. Ksh gives similar results to the other loops.

It's not the different kinds of loops that are doing it -- it's the pipe that does it. By putting your while loop behind a pipe, you are executing it inside a subshell. Values get changed in the subshell, not the main one, and don't get copied back.

ksh organizes pipes in a different order than other Bourne shells. The innermost loop runs in the current shell while the outermost loop runs in the subshell. This is a KSH-only feature.

Depending on your goal, there are various ways to circumvent or just plain avoid this. This is a useless use of ls * for instance -- a situation where you might as well be using the * operator instead of the external ls utility.

for X in *
do
        let "u+=1"
done

The 'for' loop overcomes this by putting ls in backticks, which runs it first, and sets the value in a variable. But this is not recommended as it will be confused by filenames with spaces in them.

1 Like

Aha.. makes all sense now. Thanks for explaining and that nice link

@Corona688: I too thought the same, and tried to check this by printing the pid of shell before entering while loop and inside while loop. Pid's are same. If its invoking a sub-shell it should print the pid of the sub-shell inside while loop, right?

#! /bin/bash
u=0
echo "before while, pid = $$"
ls | while read x
do
    echo "inside while, pid = $$"
    ((u++))
done
# bash --version
GNU bash, version 3.1.17(1)-release (i686-redhat-linux-gnu)
# ./test.sh
before while, pid = 6435
inside while, pid = 6435
inside while, pid = 6435
inside while, pid = 6435

Any idea what's happening here?

@balajesuri: from man bash:

1 Like

@Scrutinizer: Yes, you're right. How could I miss that!