[buug] loops and pipe sinks are subshells, aaaargh

Michael Paoli Michael.Paoli at cal.berkeley.edu
Thu Aug 4 15:52:58 PDT 2011


> From: "Ian Zimmerman" <itz at buug.org>
> Subject: Re: [buug] loops and pipe sinks are subshells, aaaargh
> Date: Thu, 04 Aug 2011 15:21:04 -0700

> Sigh.  When I wrote my post I assumed the problem I described would be
> instantly recognized by any Unix veteran who (I kept assuming) had run

Yes, instantly recognized.  I believe it's also well covered in shell
FAQ(s) ... somewhere - and I think maybe even including suggested
work-around(s) ... but I wasn't able to super easily find such FAQ,
though it likely (still) exists ... somewhere.

> into it themselves many times before, and who would be able to suggest a
> neat way around it.  I was wrong.

Feasible way(s) around it, ... "neat" is debatable.

> So let's start again.  Here's a hypothetical but reasonably practical
> example, unlike the schematic examples I posted before.

Helps to have a better idea of exactly what one's trying to solve (some
of the earlier examples could be trivially reduced to something where
the problem was totally moot - so I didn't think there was much "value"
I could add there).

> You have a program output_foo that spews lines line these:
>
> foo 1
> bar 23
> baz 4
> foo 53
> foobar 6
>  ...
>
> Note there are repetitions.  Now let's say you want a script that sums
> up all of "foo" lines, and for some reason you want to use sh (and not
> perl or python etc.)  How would you do it?  Well, the obvious way:
>
> total=0
>
> output_foo |
> while read item count ; do
>       if [ $item = foo ] ; then
>          total=`expr $total+ $count`
>       fi
> done
>
> echo $total
>
> won't work, for precisely the reasons my schematic scripts before
> failed. (It will echo 0).

Among possible approaches:

#!/bin/sh
output_foo(){
echo 'foo 1
bar 23
baz 4
foo 53
foobar 6
  ...'
}

total=0

output_foo |
echo $(
while read item count ; do
       if [ $item = foo ] ; then
          total=`expr $total + $count`
       fi
done

echo $total)

And executing that gives us output of:
54

Typically ways of handling the - I want my subshell to change my
environment or parent shell - issues are:
capture and use the results via:
stdout and/or stderr
return value
And in turn those might be directly output or written to stdout or stderr,
or use them to set shell variable(s)/parameter(s).
Alternatively (but generally not as elegant), write them to a file (and
if a temporary file, to so securely - and there isn't a fully standard
POSIX/SUS way to do that directly just with shell, but with POSIS/SUS
utilities and creating a (temporary) directory, it can be achieved, or
temporary file can be directly and securely created with certain other
utilities (I think one of which is likely in LSB)) - file can
then be used by ancestor shell - or can be final output.

Bonus points/extra credit:
Find the FAQ(s) (or a FAQ) that not only explains the whole shell pipe
(|) subshell environment/variable/parameter "issue", but also shows
(semi?-)elegant work-arounds.
Hint: news:comp.unix.shell




More information about the buug mailing list