[buug] loops and pipe sinks are subshells, aaaargh

Michael Paoli Michael.Paoli at cal.berkeley.edu
Fri Aug 5 23:28:04 PDT 2011


> From: "Michael Paoli" <Michael.Paoli at cal.berkeley.edu>
> Subject: Re: [buug] loops and pipe sinks are subshells, aaaargh
> Date: Fri, 05 Aug 2011 14:30:08 -0700

> Ian also mentioned case/example at meeting yesterday, that's less
> trivial, e.g. one wants to count for not just one string/item, but say
> several items.  I may write up an example solution for that (have it in
> my head ... just have to type, test, post ... all that requires is ...
> time - of which I usually seem to have too little).  I'll drop a hint
> on how that might be approached (and without temporary files): eval

A few more examples.

First a wee bit of a note - bash manages to screw this up and not work -
I wasted probably about 20 minutes trying to figure out what "I" did
wrong, ... when I had not done anything wrong as far as I'm aware.
Seems to be a bug even in relatively current version of bash, and also
significantly older version - and probably many other versions (present
in at least versions:
2.05b.0(1)-release
and:
3.00.15(1)-release
... likely also those between and probably before, and at least somewhat
after
) and bash messes it up even when invoked with arg0 of sh or /bin/sh or
the like, in which case it should behave in POSIXly correct manner.
The ash and dash shells seem to handle it just fine, ... and - with
minor adjustments for historical backwards compatibility, even ye old
Bourne shell from Unix 7th edition (circa 1979) handles it just fine.

So, first, we have:
$ expand -t 4 < eval
#!/bin/sh

output_foo(){
     cat <<- \__EOT__
         foo 1
         bar 23
         baz 4
         foo 53
         foobar 6
         whatever 99
         ...
         __EOT__
}

eval $(
     foo=0
     bar=0
     baz=0
     foobar=0
     output_foo |
     (
         while read item count; do
             case "$item" in
                 foo|bar|baz|foobar)
                     eval $item=\$\(expr \$$item + \$count\)
                 ;;
             esac
         done
         echo foo=$foo bar=$bar baz=$baz foobar=$foobar
     )
)

for item in foo bar baz foobar
do
     eval echo count for \$item is \$$item
done

For ye olde ancient Unix 7th edition circa 1979 Bourne shell (run under
emulation), we additionally have:
$ diff eval eval.old | expand -t 4
1c1
< #!/bin/sh
---
> :
3,15c3
< output_foo(){
<   cat <<- \__EOT__
<       foo 1
<       bar 23
<       baz 4
<       foo 53
<       foobar 6
<       whatever 99
<       ...
<       __EOT__
< }
<
< eval $(
---
> eval `
20c8
<   output_foo |
---
>   ./output_foo |
25c13
<                   eval $item=\$\(expr \$$item + \$count\)
---
>                   eval $item=\\\`expr \\\$$item + $count\\\`
31c19
< )
---
> `
$ cat output_foo
:
cat << \__EOT__
foo 1
bar 23
baz 4
foo 53
foobar 6
whatever 99
...
__EOT__

(Ye olde shell doesn't support #!notation, shell functions, and doesn't
support $( command substitution syntax.)

And the various test runs (fails miserably under bash shell - I won't
bother showing that - it apparently can't properly parse case statement
with pattern(s) in that context) ...

$ cat /etc/debian_version && ls -l /bin/sh
6.0.2
lrwxrwxrwx 1 root root 4 Apr 13 12:16 /bin/sh -> dash
$ ./eval
count for foo is 54
count for bar is 23
count for baz is 4
count for foobar is 6
$ ./eval.old
count for foo is 54
count for bar is 23
count for baz is 4
count for foobar is 6
$

And under ye olde Bourne shell from Unix 7th edition circa 1979:

$ ./eval.old
count for foo is 54
count for bar is 23
count for baz is 4
count for foobar is 6
$

I'll leave analyzing/explaining/commenting the code as an exercise.
Standard caveats/disclaimers apply.
references/excerpts:
sh(1)
expr(1)
cat(1)
http://www.rawbw.com/~mp/unix/sh/
http://www.rawbw.com/~mp/unix/sh/#Good_Programming_Practices
http://www.weak.org/pipermail/buug/2011-August/003884.html et. seq.




More information about the buug mailing list