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



Relevant Pages

  • Re: skipping first argument in a list
    ... Loops through all of the command line arguments...but how do loop skip ... issues potentially present if no subshell or separate process is used. ... echo "$file" ...
    (comp.unix.shell)
  • Re: skipping first argument in a list
    ... Loops through all of the command line arguments...but how do loop skip ... it avoids all the parameter/environment collision/pollution ... issues potentially present if no subshell or separate process is used. ... echo "$file" ...
    (comp.unix.shell)
  • Re: Variable in while loop not returning value outside the loop.?
    ... echo $total ... In the above code snippet, the last line always gives me value 0 for ... But inside the loop the variable is having correct values (as ... Because the while loop is in a pipeline, so it's executed in a subshell. ...
    (comp.unix.shell)
  • Re: Bourne Shell: scope of variables in while loop
    ... echo "this is a test" | while read line; ... The while loop is run in a subshell and it has it's own local ... pipeline is run in a subshell, so variables set inside the while loop ...
    (comp.unix.shell)
  • Re: Using foreach loop to create radiobutton menu
    ... Your foreach loop is not the problem, the problem is how you define the -command. ... The only possible thing the interpreter can do is substitute the current value of range, which is likely the last value once your loop exited. ... One choice is double quotes. ... The double quotes means that $range gets expanded while in the loop, long before the puts command actually runs. ...
    (comp.lang.tcl)