[net.unix] why doesn't this shell program work

gfs@drutx.UUCP (SkinnerGF) (03/01/85)

..........
Here is a very simple program to print out your directories


variable=none
echo "directories"
for filename
in `ls`
do
	if test -d $filename
	then	variable=some
		echo $filename
	fi
done | pr -t4
echo $variable


HOWEVER, variable will always be none wheather or not anything
was found!  further debugging shows that variable = some before
exiting the pipe but, variable = none after exiting the pipe.
Why does the pipe do this?
Thanks in advance    .....    gary skinner   drutx!gfs

henry@utzoo.UUCP (Henry Spencer) (03/03/85)

The pipe is causing the main shell to spin off a child shell to run
the innards of the for-loop.  The result is that the variable
is local to the child, and naturally doesn't propagate back into the
parent since there is no path by which it might do so.  You have just
encountered Excedrin Headache number 3.14159 for shell programmers.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (03/03/85)

> for ...
> do
> 	variable=some
> done | ...
> echo $variable

The problem is that `variable' is being set in a forked subshell.
I don't know of any simple way around this.

johnl@ima.UUCP (03/04/85)

Congratulations!  You have tripped over one of the most peculiar parts
of the Bourne shell.  The problem is this:  the shell only knows how to
do I/O redirection for processes, not for builtin commands.  This means
that if you redirect I/O for a command, it gets forked into a subprocess
silently and confusingly.

For example:

a=one

for i in two three four
do
	a=$i
done > plugh

echo $a

The result is "one" because the for loop is done in a subprocess, and the
modifications to variable a in the subprocess don't affect the top level
shell.  See Steve Bourne's book "The Unix System" for some extremely gross
ways to work around this.  (I suppose that since it's his shell, he gets to
program it any way he wants, though.)

Also, the System V shell is somewhat smarter about I/O redirection, so
some things like "read foo <bar" which used not to work now do.

John Levine, ima!johnl

terryl@tekcrl.UUCP () (03/04/85)

>variable=none
>echo "directories"
>for filename
>in `ls`
>do
>	if test -d $filename
>	then	variable=some
>		echo $filename
>	fi
>done | pr -t4
>echo $variable
>
>HOWEVER, variable will always be none wheather or not anything
>was found!  further debugging shows that variable = some before
>exiting the pipe but, variable = none after exiting the pipe.
>Why does the pipe do this?

     Because of the pipe, the shell has to fork off two processes for both
sides of the pipe, i.e. the for loop and the pr command. So, what is in reality
happening is that there are three processes running: the original process for
the shell script, the first subprocess for the for loop, and the second sub-
process for the pr command. Since you are executing the for loop in a sub-
process, all variables set inside the for loop only have their true value as
long as the for loop is running. If you take out the pipe, then things work
as you expected.

					Terry Laskodi
					     of
					Tektronix

quenton@ecr.UUCP (Dennis Smith) (03/05/85)

The shell file in question piped a "for" loop into "pr".  Inside the
"for" loop, a shell variable was set.  The question was - why is the
variable not set after the "for .. | pr" is over?

One must recognize when the shell, in its perverted manner, fires up
new processes or forked copies of itself to do things.  In this case,
the shell has obviously forked a copy of itself to handle the "for",
because it is piped.  Thus the variable set inside the for was
actually set (and lost) in another shell process.

It would seem that sh does not really have to do this unless the
whole mess was followed by &, but it seems to.

mab@druxp.UUCP (BlandMA) (03/07/85)

Just to confuse matters a little more, ksh (the Korn shell) handles
this situation better than the Bourne shell, but still isn't perfect.
ima!johnl gave the following example:

	a=one
	for i in two three four
	do
		a=$i
	done >plugh
	echo $a

Using sh, the result is a=one.  But ksh gives the desired result
of a=four.  In the interest of efficiency, ksh tries not to fork extra
shells when possible, and this is apparently one of those situations.
Unfortunately, the following slightly modified version of the example,
using a pipe instead of a file for output, results in a=one with both
sh and ksh.  Sigh.

	a=one
	for i in two three four
	do
		a=$i
	done | cat
	echo $a

Hey!  Maybe we can add an option to "cat" to make this work!  :-)
-- 
Alan Bland
{ihnp4, allegra}!druxp!mab
AT&T Information Systems Labs, Denver