Sh vs bash

I don't know if this is an anomaly or by design.

bash-3.2# cat t                             
#!/bin/sh                                   
wc -l wpd*.prg >wc.out                      
totallines=0                                
totalprg=0                                  
while read a b                              
do                                          
        totalprg=`expr $totalprg + 1`       
        totallines=`expr $totallines + $a`  
        echo $totalprg $totallines          
done <wc.out                                
echo Total Programs $totalprg               
echo Total Lines $totallines                
bash-3.2# ./t                               
1 3146                                      
2 6299                                      
3 9453                                      
4 18906                                     
Total Programs 0                            
Total Lines 0                               
bash-3.2#        
  bash-3.2# cat t                           
#!/bin/bash                               
wc -l wpd*.prg >wc.out                    
totallines=0                              
totalprg=0                                
while read a b                            
do                                        
        totalprg=`expr $totalprg + 1`     
        totallines=`expr $totallines + $a`
        echo $totalprg $totallines        
done <wc.out                              
echo Total Programs $totalprg             
echo Total Lines $totallines              
bash-3.2# ./t                             
1 3146                                    
2 6299                                    
3 9453                                    
4 18906                                   
Total Programs 4                          
Total Lines 18906                         
bash-3.2#             

and finally

bash-3.2# cat t                            
#!/bin/sh                                  
#wc -l wpd*.prg >wc.out                    
totallines=0                               
totalprg=0                                 
while read a b                             
do                                         
        totalprg=`expr $totalprg + 1`      
        totallines=`expr $totallines + $a` 
        echo $totalprg $totallines         
done                                       
echo Total Programs $totalprg              
echo Total Lines $totallines               
bash-3.2# ./t <wc.out                      
1 3146                                     
2 6299                                     
3 9453                                     
4 18906                                    
Total Programs 4                           
Total Lines 18906                          
bash-3.2#                                  
2 Likes

There are various threads going around that draw attention to how Bash spawns a sub-shell to handle while-loops, etc, which is why the value outside a while-loop is "not preserved".

That does not apply when 'while read' is only reading from a file, Scott.

What is 'sh' on your system? So far BASH, ASH, and DASH all work here.

1 Like

What version/type of shell are you using in your 1st example (/bin/sh) ?

I get result 2 with everything I have here ash, dash, ksh, bash and bash --posix

Strangely enough I found this some time ago in the early stages of the AudioScope.sh development:-

Last login: Wed Aug  6 22:34:25 on ttys000
AMIGA:barrywalker~> for n in {1..10}; do printf "$n\n"; n=$((n+2)); done
1
2
3
4
5
6
7
8
9
10
AMIGA:barrywalker~> _

I still don't understand what is happening, as I would have thought that the "n" inside the loop would either create an error or be the equivalent of a "step of 2".
Would this be a bug or am I missing something...

I ran the original scripts on SCO 6.0.0 mp4
On SUSE 11.2 I get the expected results using sh.

@wisecracker remember that a for loop is just fetching the variable as a string from the parameter list one at a time it isn't incrementing the variable at all.

So you can see that changing n within the for loop will have no effect on the next parameter fetched, think about:

for n in A B C D
do
   ...
   n=Z
done

@jgt, was /bin/sh a linked to another shell?

@wisecracker. No matter what you assign to n inside the loop, it does not matter, since n is given its next value by the for statement just before printing, but this is off topic and should be in another thread...

I think it was the original Bourne shell. I just tried it on Solaris 10 (where /bin/sh is the classic Bourne Shell):

$ /bin/sh ./t | grep Total
Total Programs 0
Total Lines 0
$ /usr/xpg4/bin/sh ./t | grep Total
Total Programs 50
Total Lines 787310
$ bash ./t | grep Total
Total Programs 50
Total Lines 787310
$ ksh ./t | grep Total
Total Programs 50
Total Lines 787310

So apparently the Bourne Shell is using a subshell for a while loop even when only redirecting input..

SCO ships with 3 Bourne Shells.
/u95/bin/sh which is aware of files larger than 2gb
/usr/bin/posix/sh which I never knew about.
/bin/sh which is the default shell when creating new users and is the only one that displays this problem.

/usr/bin/posix/sh must be a Posix shell or a link to one.

According to this SCO 7 (UnixWare 7) man page:
sh(1)

So it appears of the three only /bin/sh is a Bourne Shell (probably a link to /usr/bin/sh or vice versa)

In my experience, on Linux sh is just a soft link to bash.

Interesting. So this basic, fundamental behavior which most everyone would depend on today is not fundamental Bourne behavior! It's probably POSIX but not old-fashioned, bug-compatible-with-1970 Bourne.

This is oftenly true but sometimes it is not. On Ubuntu-based systems (and maybe all other Debian-based ones too?) "sh" is a "dash".

I hope this helps.

bakunin

It is not just a softlink. When sh is a symlink to bash on certain Linux distributions and sh is invoked, bash is run with the --posix option.

This is an interesting example. It shows that pure Bourne shells execute while loops in a subshell environment when the input to that loop is redirected from a file, but does not create a subshell when the input is the script's standard input. If I knew that Bourne shell "feature", I had forgotten it.

An ugly work-around is this descriptor-magic:

#!/bin/sh
wc -l wpd*.prg >wc.out
totallines=0
totalprg=0
exec 3<&0 # save stdin
exec 0<wc.out # redirect stdin
while read a b
do
        totalprg=`expr $totalprg + 1`
        totallines=`expr $totallines + $a`
        echo $totalprg $totallines
done
exec 0<&3 # restore stdin
echo Total Programs $totalprg
echo Total Lines $totallines

I wonder how old read's -u flag is. It might have been invented as a workaround to this problem, it'd simplify your solution a bit.

#!/bin/sh
wc -l wpd*.prg >wc.out
totallines=0
totalprg=0

exec 5<wc.out
while read -u 5 a b
do
        totalprg=`expr $totalprg + 1`
        totallines=`expr $totallines + $a`
        echo $totalprg $totallines
done
exec 5<&-
echo Total Programs $totalprg
echo Total Lines $totallines

Pure Bourne shells didn't have a -u option for read . I'm not sure where or when it first appeared, but it was in ksh88 .

Right. I always thought this to be one of the definitive advantages of ksh (at least for scripting) over all the other shells, because with "print -u" and "read -u" you can easily put all the I/O-descriptors to work. In Bourne-shell as well as in bash this was always a hassle. Picture the equivalent like:

exec 3>file.1
exec 4>file.2
exec 5>file.3

print -u3 - "this goes to file.1"
print -u4 - "this goes to file.2"
print -u5 - "this goes to file.3"
print -u3 - "this goes to file.1 again"

exec 3>&-
exec 4>&-
exec 5>&-

in bash. It is not impossible, but the code would be a lot less clean. In addition you'd need different redirections (one overwriting, one appending) for the two lines going to file.1.

bakunin