[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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


Reply to: