Shell scripts: variable assignment within read loops

David Wolfskill david at catwhisker.org
Mon Aug 18 01:47:21 UTC 2008


I am writing a (Bourne) shell script that is intended (among other
things) to obtain information from a command, such as:

	netstat -nibd -f inet

by reading and parsing the output.

However, the "obvious" (to me) approach of piping the output of the
command to the standard input of a "while read ..." statement turns out
to be not very useful; it appears that while

	foo=""
	while read bar ... ; do
	 ...
	  foo=$bar
	 ...
	done <$filename
	echo $foo

will assign to foo the value of the bar variable form the last record
read (in FreeBSD 6.3-STABLE, at least), the following fails to do so:

	foo=""
	cat $filename | while read bar ... ; do
	 ...
	  foo=$bar
	 ...
	done
	echo $foo

Well, that's not *quite* accurate:the assignment is done all right, but
in the latter case, it appears to be done in a subshell, so by the time
we get to the "echo" statement, any variable assignments from within the
read loop have vanished.

Here's a cut/pasted, somewhat contrived example:

#! /bin/sh

foo=0
echo "0 foo: $foo"
while read line; do
  echo "1.0 foo: $foo"
  foo=1$line
  echo "1.1 foo: $foo"
done </etc/resolv.conf
echo "2 foo: $foo"

echo ""

foo=2
echo "3 foo: $foo"
eval "cat /etc/resolv.conf | grep '.'"
cat /etc/resolv.conf | while read line; do
  echo "4.0 foo: $foo"
  foo=3$line
  echo "4.1 foo: $foo"
done
echo "5 foo: $foo"

exit 0

and its output on my laptop:

0 foo: 0
1.0 foo: 0
1.1 foo: 1search catwhisker.org
1.0 foo: 1search catwhisker.org
1.1 foo: 1nameserver 172.16.8.12
1.0 foo: 1nameserver 172.16.8.12
1.1 foo: 1nameserver 172.16.8.11
1.0 foo: 1nameserver 172.16.8.11
1.1 foo: 1nameserver 172.16.8.1
2 foo: 1nameserver 172.16.8.1

3 foo: 2
search catwhisker.org
nameserver 172.16.8.12
nameserver 172.16.8.11
nameserver 172.16.8.1
4.0 foo: 2
4.1 foo: 3search catwhisker.org
4.0 foo: 3search catwhisker.org
4.1 foo: 3nameserver 172.16.8.12
4.0 foo: 3nameserver 172.16.8.12
4.1 foo: 3nameserver 172.16.8.11
4.0 foo: 3nameserver 172.16.8.11
4.1 foo: 3nameserver 172.16.8.1
5 foo: 2

Note that the "2 foo" line shows a value read from the last line of
/etc/resolv.conf, while the "5 foo" line fails to do so.  (In a Solaris
9 environment, the output from each stanza is the same as the output
from teh second stanza in FreeBSD.)

Now here's a copy of the in-development script:

#! /bin/sh

cmd="netstat -nibd -f inet"
ctr=0
clist=""
hlist=`$cmd | head -1`
for f in $hlist; do
  ctr=$(( $ctr + 1 ))
  eval c$ctr=\"$f\"
  eval h_$f=c$ctr
done
cmax=$ctr

t_file=`mktemp /tmp/XXXXXXXXX`
$cmd | tail +2 >$t_file
while read $hlist dummy; do
  if [ "$Name" = "lo0" ]; then
    continue
  fi
  for f in $hlist; do
    eval val=\"\$$f\"
    case $val in
    -) eval ${f}_$Name=0;;
    *) eval ${f}_$Name="$val";;
    esac;
  done
  nics="$Name $nics";
done</$t_file
rm $t_file
echo "(end) NICs: $nics"
for n in $nics; do
  for f in $hlist; do
    eval "echo ${f}_$n: \$${f}_$n"
  done
done

exit 0

And its output on my laptop:

(end) NICs: ath0 
Name_ath0: ath0
Mtu_ath0: 1500
Network_ath0: 172.17
Address_ath0: 172.17.1.30
Ipkts_ath0: 725191
Ierrs_ath0: 0
Ibytes_ath0: 185144197
Opkts_ath0: 821917
Oerrs_ath0: 0
Obytes_ath0: 74260936
Coll_ath0: 0
Drop_ath0: 0

and (somewhat more interestingly) on my "firewall" machine:

(end) NICs: dc0 de0 fxp0 
Name_dc0: dc0
Mtu_dc0: 1500
Network_dc0: 172.16.8/24
Address_dc0: 172.16.8.1
Ipkts_dc0: 2501577
Ierrs_dc0: 0
Ibytes_dc0: 215386153
Opkts_dc0: 20269087
Oerrs_dc0: 0
Obytes_dc0: 2553930555
Coll_dc0: 0
Drop_dc0: 0
Name_de0: de0
Mtu_de0: 1500
Network_de0: 63.193.123/24
Address_de0: 63.193.123.122
Ipkts_de0: 5936847
Ierrs_de0: 0
Ibytes_de0: 734092787
Opkts_de0: 18557543
Oerrs_de0: 0
Obytes_de0: 2551089632
Coll_de0: 0
Drop_de0: 0
Name_fxp0: fxp0
Mtu_fxp0: 1500
Network_fxp0: 172.17
Address_fxp0: 172.17.0.1
Ipkts_fxp0: 10013
Ierrs_fxp0: 0
Ibytes_fxp0: 1366082
Opkts_fxp0: 1253115
Oerrs_fxp0: 0
Obytes_fxp0: 70429903
Coll_fxp0: 0
Drop_fxp0: 0


As you see, I am circumventing the issue by writing to a transient file.
In the intended application, the script is to be used to gather
resource-utilization information; thus, I want its "footprint" to be
smaller, rather than larger.  Granted, in my case, I would be writing a
tiny text file to a swap-backed tmpfs, but in production, I won't have
the luxury of knowing that in advance: the intent is that the script
must run on a minimal FreeBSD system, with no "ports" or other 3rd-party
software installed.

Is there some other -- possibly better -- way to do this (using Bourne
shell scripting)?

Thanks!

(Please select recipients for your replies carefully: I'm not subscribed
to -questions.  I've provided a hint in the form of a Reply-To header,
though I realize that not all mailers honor it.  Please do include me in
replies.)

Peace,
david
-- 
David H. Wolfskill				david at catwhisker.org
Depriving a girl or boy of an opportunity for education is evil.

See http://www.catwhisker.org/~david/publickey.gpg for my public key.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
Url : http://lists.freebsd.org/pipermail/freebsd-questions/attachments/20080818/df6c2c86/attachment.pgp


More information about the freebsd-questions mailing list