[comp.unix.questions] Bourne Shell FOR loop confusion

kevin@msa3b.UUCP (Kevin P. Kleinfelter) (08/01/89)

Would anyone who knows, please explain the following behavior?
(Post or e-mail, as you deem appropriate.)

I enter the following:
    x=x
    for i in abc
    do
       x=$i
    done
    echo $x
The output is, as expected, "abc".

Then I enter the following:
    y=y
    for i in abc
    do
       y=$i
    done < /dev/null
    echo $y
The output is "y", which is exactly what I want to know! ("why").

*****************************************
What I really want to do is to redirect input to "read", but read is
non-redirectable (according to my doc, and experience on my system).
I was going to attemtpt this as follows

   for i in 1
   do
      read a b c
   done < /tmp/foo

If I "echo a, b, c" inside the loop, I get the expected values from the file.
If I "echo a, b, c" after the loop, I see that they have reverted to their
values prior to the loop.

Korn Shell has a nice way to resolve this, via an option to "read" to read from
a specific file handle.  Bourne Shell doesn't.  I'm stuck with a system
that does not have Korn Shell. :-(
-- 
Kevin Kleinfelter @ Management Science America, Inc (404) 239-2347
gatech!nanovx!msa3b!kevin

bob@wyse.wyse.com (Bob McGowen Wyse Technology Training) (08/04/89)

In article <689@msa3b.UUCP> kevin@msa3b.UUCP (Kevin P. Kleinfelter) writes:
>Would anyone who knows, please explain the following behavior?
>(Post or e-mail, as you deem appropriate.)
---example code deleted---
>
>*****************************************
>What I really want to do is to redirect input to "read", but read is
>non-redirectable (according to my doc, and experience on my system).
---deleted---

A little know fact about re-directing input to/from an sh loop is
that the shell automatically forks another sh to run the loop.  Thus,
variables changed in the loop do NOT affect variables of the same
name in the parent shell.

May I suggest the following code to perform your desired redirction
to the read command (btw, some newer versions of sh appear to support
redirection to the read built-in --- several XENIX scripts I have
looked at use this feature):

	while read line
	do
	   ...
	done < file

read returns failure on EOF and the loop then exits.  Just remember
that what you do in the loop may not be available to your script
outside of the loop.  One way around this would be to save items
in a tmp file and either read the tmp file as needed or perhaps
use the dot command on it:

	. tmp

Hope this is helpful.

Bob McGowan  (standard disclaimer, these are my own ...)
Customer Education, Wyse Technology, San Jose, CA
..!uunet!wyse!bob
bob@wyse.com

maart@cs.vu.nl (Maarten Litmaath) (08/04/89)

kevin@msa3b.UUCP (Kevin P. Kleinfelter) writes:
\...
\   for i in 1
\   do
\      read a b c
\   done < /tmp/foo
\
\If I "echo a, b, c" inside the loop, I get the expected values from the file.
\If I "echo a, b, c" after the loop, I see that they have reverted to their
\values prior to the loop.

Due to the IO redirection the `for' command is executed in a subshell... :-(

\Korn Shell has a nice way to resolve this, via an option to "read" to read
\from a specific file handle.  Bourne Shell doesn't.  I'm stuck with a system
\that does not have Korn Shell. :-(

Here's an example how to do it:

	# save old stdin and IFS
	exec 3<&0
	OIFS="$IFS"

	entry='login passwd uid gid gecos home shell'
	IFS=:
	exec 0< /etc/passwd
	eval read $entry

	# restore IFS and stdin
	IFS="$OIFS"
	exec 0<&3

	read a b c

	for i in $entry a b c
	do
		eval echo $i=\$$i
	done

In modern Bourne shells you simply use:

	read foo < bar
-- 
"Mom! Eric Newton broke the day! In 24   |Maarten Litmaath @ VU Amsterdam:
  parts!" (Mike Schmitt in misc.misc)    |maart@cs.vu.nl, mcvax!botter!maart

jdpeek@RODAN.ACS.SYR.EDU (Jerry Peek) (08/13/89)

In article <2346@wyse.wyse.com> bob@wyse.UUCP (Bob McGowen Wyse Technology Training) writes:
> ... what you do in the loop may not be available to your script
> outside of the loop.  One way around this would be to save items
> in a tmp file and either read the tmp file as needed or perhaps
> use the dot command on it:
> 
> 	. tmp

I think this is a good technique to know, so I decided to post an
example of the fix I use.  Inside the loop, I use "echo" commands to
spit out variable-definition commands.  I hook the output of 'echo'
onto a less-used file descriptor like 4 -- and catch fd4 into a file
at the end of the loop.

This code fragment reads in /etc/passwd.  After the loop, you can get
the first username from $user1, the first uid in $uid1, 20th username
from $user20, and so on...  This particular example is a quick-and-dirty
way to get Bourne Shell "arrays", but it works just as well for plain ol'
single-value shell variables:

	defs=/tmp/defs$$
	IFS=":$IFS"   # TO PARSE :-SEPARATED FIELDS
	line=0

	# READ /etc/passwd A LINE AT A TIME; STORE GOOD STUFF IN
	# VARIABLES IN $defs FILE BECAUSE LOOP IS RUN IN A SUB-SHELL:
	while read user passwd uid junk
	do
	   line=`expr $line + 1`
	   echo "user${line}='$user' uid${line}='$uid'" 1>&4
	done </etc/passwd 4>$defs

	. $defs   # READ VARIABLE DEFINITIONS INTO THIS SHELL

The $defs file gets lines like this:
	user23='freddie' uid23='1032'
	user24='alice' uid24='1048'
then, after the ". $defs" command, you can access $user23, $uid23, etc.

BTW, it's more efficient to use "echo 1>&4" and "done 4>$defs",
because the $defs file is only opened once while the loop runs.
On the other hand, if you simply put "echo >>$defs" into the loop,
the shell has to open and close the $defs file for each pass of the loop.

--Jerry Peek; Syracuse University Academic Computing Services; Syracuse, NY
  jdpeek@rodan.acs.syr.edu///JDPEEK@SUVM.BITNET///GEnie: J.PEEK1
  +1 315 443-3995

andre@targon.UUCP (andre) (08/14/89)

In article <689@msa3b.UUCP> kevin@msa3b.UUCP (Kevin P. Kleinfelter) writes:
>When I enter the following:
>    y=y
>    for i in abc
>    do
>       y=$i
>    done < /dev/null
>    echo $y
>The output is "y", which is exactly what I want to know! ("why").

As long as a loop in the shell uses the stdin stdout and stderr of
that shell, the loop is executed by that shell, your loop however
redirects its stdin. The shell solves this by spawning a subshell that
has its stdin redirected. Your second loop is thus executed by a subshell
and the value of y stays "y".
If you echo the value of y inside the loop you see the right value, but
that value cannot be transferred back to the parent shell. Maybe you
can try something like,

    y=y
    y=`(
	for i in abc
	do
	   y=$i
	done
	echo $y
       ) < /dev/null `

I let the </dev/null be there to show where you must put the redirection now.


-- 
    \---|    AAA         DDDD  It's not the kill, but the thrill of the chase.
     \  |   AA AAvv   vvDD  DD        Ketchup is a vegetable.
  /\  \ |  AAAAAAAvv vvDD  DD                    {nixbur|nixtor}!adalen.via
_/__\__\| AAA   AAAvvvDDDDDD    Andre van Dalen, uunet!hp4nl!targon!andre

maart@cs.vu.nl (Maarten Litmaath) (08/14/89)

jdpeek@RODAN.ACS.SYR.EDU (Jerry Peek) writes:
\...	while read user passwd uid junk
\	do
\	   line=`expr $line + 1`
\	   echo "user${line}='$user' uid${line}='$uid'" 1>&4
\	done </etc/passwd 4>$defs

Why use fd 4, Jerry?  Stdout will do.
-- 
"Mom! Eric Newton broke the day! In 24   |Maarten Litmaath @ VU Amsterdam:
  parts!" (Mike Schmitt in misc.misc)    |maart@cs.vu.nl, mcvax!botter!maart

jim@hp-ptp.HP.COM (James_Rogers) (08/17/89)

/ hp-ptp:comp.unix.questions / kevin@msa3b.UUCP (Kevin P. Kleinfelter) /  6:16 am  Aug  1, 1989 /
>What I really want to do is to redirect input to "read", but read is
>non-redirectable (according to my doc, and experience on my system).
>I was going to attemtpt this as follows
>
>   for i in 1
>   do
>      read a b c
>   done < /tmp/foo
>
>If I "echo a, b, c" inside the loop, I get the expected values from the file.
>If I "echo a, b, c" after the loop, I see that they have reverted to their
>values prior to the loop.

You could try the following form of redirection:

for nextfile in a b c
do
	cat $nextfile | {
		while [ -r $nextfile ]
		do
			read aline
			if [ $? -ne 0 ]
			then
				break
			fi
			echo $aline
		done
	}
done


I know that this is not pretty, but it does allow you to read through
files a, b, and c from the Bourne shell.



Jim Rogers  at Hewlett Packard Industrial Applications Center
	       Sunnyvale, California

jdpeek@RODAN.ACS.SYR.EDU (Jerry Peek) (08/21/89)

In article <2999@solo1.cs.vu.nl> maart@cs.vu.nl (Maarten Litmaath) writes:
< jdpeek@RODAN.ACS.SYR.EDU (Jerry Peek) writes:
< \...	while read user passwd uid junk
< \	do
< \	   line=`expr $line + 1`
< \	   echo "user${line}='$user' uid${line}='$uid'" 1>&4
< \	done </etc/passwd 4>$defs
< 
< Why use fd 4, Jerry?  Stdout will do.

Oops.  To keep from wasting net bandwidth, I left out things like the
error checking (with output to stderr, fd2):
	echo "`basename $0`: warning: bad line $line" 1>&2
Also, the stdout of the loop (fd1) went to a pipe:
	done </etc/passwd 4>$defs |
	some-other-program-or-loop
That's why I used fd4 for the variable definitions.

BTW, Chris Torek sent mail and pointed out that using fd4 takes time for
the extra dup() calls that the shell has to do.  Chris didn't know that
I'd left out some of the code, but he still has a good point.

--Jerry Peek; Syracuse University Academic Computing Services; Syracuse, NY
  jdpeek@rodan.acs.syr.edu///JDPEEK@SUVM.BITNET///GEnie: J.PEEK1
  +1 315 443-3995