Re: Fascinating problem with bash



On 08/24/2010 04:09 AM, Oliver Schneider wrote:
Hello Cameron, Bob,

As soon as I read this paragraph I saw the problem. I confirmed it
looking at the code. It's a common problem.

This construct:

some_cmd | while read var ; do
OTHER_VAR=...
done

will result in OTHER_VAR being unset at the completion of the loop. That
is because the while command is on the right-hand side of the pipe
meaning it runs in a subshell. At the end of the while loop, the
subshell exits and any vars it sets will go away with it.
Okay, that is surprising indeed, as SHLVL is not being adjusted to reflect
that fact, according to my findings. But thanks a bunch for pointing that
out. It's surely more elegant to use this method than to write to a
temporary
file.

Also thanks to Bob for providing the links. Very useful, noted them down.


Thanks a lot,

// Oliver

All this got me to wondering, so I looked at the two links Bob provided.
And, I did some tests of my own.

First, I think there's an error on the SubShell page, in the "example"
of the difference between a "subshell" and a full "child process", at
the end. The author uses double quotes for the subshell, then single
and double quotes for the child process. It's the single quotes that
prevent evaluation of $a, not the "child process" versus "subshell".

If you use single quotes in the subshell line, the $a is printed as is:

$ (echo 'a is $a in subshell')
a is $a in subshell
$

Since the double quotes in the child process example are not needed,
removing them and replacing the single quotes with double quotes results
in output with $a replaced by it's value, 1.

$ sh -c "echo a is $a in child"
a is 1 in child
$

Getting quoting right in shell scripts is often difficult. ;-)

This is the code used for my testing. Note I use double quotes only and
backslashes when I want to "quote" specific single characters to prevent
evaluation. The quoting forces the use of 'eval' in the 'while' loop's
first echo, to force variable substitution to happen when the loop is
run, otherwise the output would be strings, $$ and $SHLVL, literally.

====
#!/bin/bash

SHLVL=1 # I'm using ksh which is setting this to 2, in GUI env.
# This also means you may not want to trust the value, in some cases.
for n in 1
do
echo iteration: $n pid1 is $$ SHLVL is $SHLVL
echo $n | while read m
do
MyVar='while loop'
eval echo "iteration: $m and pid2 is \$$ SHLVL is \$SHLVL"
bash -c "echo parent is $$ I\'m \$$ SHLVL is \$SHLVL"
if [ "$MyVar" ]
then
echo $MyVar
else
echo MyVar is empty
fi
done | cat # Just to put the loop between two pipes.
if [ "$MyVar" ]
then
echo $MyVar
else
echo MyVar is empty
fi
done
====

The results of a run:

====
iteration: 1 pid1 is 23853 SHLVL is 1
iteration: 1 and pid2 is 23853 SHLVL is 1
parent is 23853 I'm 23857 SHLVL is 2
while loop
MyVar is empty
====

The only point where SHLVL, and $$, get 'reset', is in the explicit
execution of 'bash -c'.

I believe this suggests modern shells are maintaining the functionality
of a "subshell", but are running things in the "current" process, for
reasons of efficiency.

Or, I'm completely off my rocker (possible) and not getting it (also
possible). If there's a better explanation, I'd like to see it ;)

Thanks,

--
Bob McGowan


--
To UNSUBSCRIBE, email to debian-user-REQUEST@xxxxxxxxxxxxxxxx
with a subject of "unsubscribe". Trouble? Contact listmaster@xxxxxxxxxxxxxxxx
Archive: http://lists.debian.org/4C7417CB.5090509@xxxxxxxxxxxx