Shell scripts: variable assignment within read loops

Oliver Fromme olli at lurza.secnetix.de
Tue Aug 19 13:54:05 UTC 2008


David Wolfskill wrote:
 > 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;
 > [...]
 > 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.

That's correct, as Giorgos has already pointed out.
Most bourne shells execute all parts of a pipe except
the first one in a subshell, so any assignments are
lost.

A common way is to echo things from within the subshell
and capture them through command expansion, like this:

   foo=$(
           something | while read x; do
               whatever
               echo "value"
           done
   )

That will assign "value" to the variable foo.  This only
works for single variables, of course.  If you want to
assign to multiple variables, it gets a little more tricky.
One way is to use single assignment like above, and then
split it into several variables on a delimiter character.
The following will split $foo on whitespace and assign
the results to $1, $2, $3 etc., with the count in $#:

   set -- $foo

You can split on any other character by setting the IFS
variable of the shell appropriately.

If you know in advance how many values you'll get, another
possibility is to use a so-called "here document":

   read foo1 foo2 foo3 rest <<end
       $(
           something | while read x; do
               whatever
               echo -n "value "
           done
       )
   end

For example, a simple way to get hour, minutes and seconds
into three variables without having to exec date(1) three
times:

   read H M S <<end
       $( date +"%H %M %S" )
   end

Or:

   set -- $( date +"%H %M %S" )
   H=$1
   M=$2
   S=$3

It gets more complicated if you need to get the exit code
of some parts of the pipe except the last one.  You didn't
ask for that, though.  :-)

Best regards
   Oliver

-- 
Oliver Fromme, secnetix GmbH & Co. KG, Marktplatz 29, 85567 Grafing b. M.
Handelsregister: Registergericht Muenchen, HRA 74606,  Geschäftsfuehrung:
secnetix Verwaltungsgesellsch. mbH, Handelsregister: Registergericht Mün-
chen, HRB 125758,  Geschäftsführer: Maik Bachmann, Olaf Erb, Ralf Gebhart

FreeBSD-Dienstleistungen, -Produkte und mehr:  http://www.secnetix.de/bsd

'Instead of asking why a piece of software is using "1970s technology,"
start asking why software is ignoring 30 years of accumulated wisdom.'


More information about the freebsd-questions mailing list