[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