Re: Shell scripts: variable assignment within read loops

2008-08-21 Thread Jan Grant
On Sun, 17 Aug 2008, David Wolfskill wrote:

[snipped]
 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.

You've already defined the reason behind this. Since subshells for parts 
of pipelines aren't guaranteed, you need something more clever. Assuming 
you're only interested in the _last_ value of foo, you could try this:

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

Cheers,
jan


-- 
jan grant, ISYS, University of Bristol. http://www.bris.ac.uk/
Tel +44 (0)117 3317661   http://ioctl.org/jan/
( echo ouroboros; cat )  /dev/fd/0 # it's like talking to yourself sometimes
___
freebsd-questions@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-questions
To unsubscribe, send any mail to [EMAIL PROTECTED]


Re: Shell scripts: variable assignment within read loops

2008-08-19 Thread Oliver Fromme
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.'
___
freebsd-questions@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-questions
To unsubscribe, send any mail to [EMAIL PROTECTED]


Re: Shell scripts: variable assignment within read loops

2008-08-19 Thread CyberLeo Kitsana
David Wolfskill wrote:
   foo=
   cat $filename | while read bar ... ; do
...
 foo=$bar
...
   done
   echo $foo
 

A trick I've used to great advantage in bourne shell and bash for
passing multiple variables back is to produce small snippets of shell
script within a function, such as the following, for pulling in a bunch
of variables with a single program invocation for efficiency:



get_stats(){
 stat -fc 'mount=%n blksz=%S total=%b free_root=%f \
  free_user=%a' [EMAIL PROTECTED]
}

get_stats / /dev /tmp | while read line
do
 eval ${line}
 # now mount, blksz, total, free_root, and free_user are set here.
 printf = %s has %u free %u-byte blocks, out of %u\n \
  ${mount} ${free_user} ${blksz} ${total}
done



The function returns a series of lines that can be iterated with 'while
read', and evaluated individually for action. If the function returns
only a single line, it can be passed directly into eval:



eval $(get_stats /)



As this shortcut does execute arbitrary code, however, there is always a
chance that it can be hijacked for nefarious purposes if the data source
is untrusted.

-- 
Fuzzy love,
-CyberLeo
Technical Administrator
CyberLeo.Net Webhosting
http://www.CyberLeo.Net
[EMAIL PROTECTED]

Furry Peace! - http://.fur.com/peace/
___
freebsd-questions@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-questions
To unsubscribe, send any mail to [EMAIL PROTECTED]


Re: Shell scripts: variable assignment within read loops

2008-08-18 Thread Polytropon
As I thought while reading your message, awk seems to be
a good solution. Just a note:

On Mon, 18 Aug 2008 06:29:03 +0300, Giorgos Keramidas [EMAIL PROTECTED] wrote:
 Would you
 be ok with an awk(1) script instead of /bin/sh?  It tends to be nicer
 for this sort of thing, i.e.:
 
 [...]
 $ netstat -nibd -f inet | awk -f david.awk

You could start your awk skript with

#!/usr/bin/awk

and give it +x attribute, as well as adding the desired source
command netstat -nibd -f inet to the script, using awk's system()
function, so you can start it more easily or use it in combination
with other commands.

% ./netstuff.awk


-- 
Polytropon
From Magdeburg, Germany
Happy FreeBSD user since 4.0
Andra moi ennepe, Mousa, ...
___
freebsd-questions@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-questions
To unsubscribe, send any mail to [EMAIL PROTECTED]


Re: Shell scripts: variable assignment within read loops

2008-08-18 Thread Giorgos Keramidas
On Mon, 18 Aug 2008 14:33:05 +0200, Polytropon [EMAIL PROTECTED] wrote:
 As I thought while reading your message, awk seems to be
 a good solution. Just a note:

 On Mon, 18 Aug 2008 06:29:03 +0300, Giorgos Keramidas [EMAIL PROTECTED] 
 wrote:
 Would you
 be ok with an awk(1) script instead of /bin/sh?  It tends to be nicer
 for this sort of thing, i.e.:

 [...]
 $ netstat -nibd -f inet | awk -f david.awk

 You could start your awk skript with

   #!/usr/bin/awk

 and give it +x attribute, as well as adding the desired source
 command netstat -nibd -f inet to the script, using awk's system()
 function, so you can start it more easily or use it in combination
 with other commands.

   % ./netstuff.awk

That's an option, but piping *to* awk may be slightly trickier then.


___
freebsd-questions@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-questions
To unsubscribe, send any mail to [EMAIL PROTECTED]


Re: Shell scripts: variable assignment within read loops

2008-08-17 Thread Giorgos Keramidas
On Sun, 17 Aug 2008 18:33:28 -0700, David Wolfskill [EMAIL PROTECTED] 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; 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.

Hi David,

You are right that feeding data to a looping construct through a pipe
may run in a subshell.  The ``Single UNIX Specification'' says

... each command of a multi-command pipeline is in a subshell
environment; as an extension, however, any or all commands in a
pipeline may be executed in the current environment.

You can read the online text of the Single UNIX Specification at:

http://www.unix.org/single_unix_specification/

A simple 'registration' is required, and you need a browser that
supports cookies.  But after that, you can follow the links to the shell
command language page at

http://www.opengroup.org/onlinepubs/95399/utilities/xcu_chap02.html

Near section `` 2.12 Shell Execution Environment'' you can find the text
I quoted.

What I usually do in similar shell scripts is something like:

cat ${filename} | sed -n -e '/foo/ s/bar/baz/' | \
xargs -n1 blah

This isn't exactly the same as assigning $foo to the results of the
loop, but you can also use:

foo=`cat $filename | while read bar ; do \
 stuff ...
 echo $bar
 more stuff...
 done`

 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/X`
 $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)?

Ah, that's much better.  Now I see what you are trying to do.  Would you
be ok with an awk(1) script instead of /bin/sh?  It tends to be nicer
for this sort of thing, i.e.:

$ expand david.awk | cat -n
 1  #
 2  # Gather the field names if this is a header-line.
 3  #
 4  $0 ~ /^Name/ {
 5  for (k = 1; k = NF; k++)
 6  tag[k] = $k;
 7  }
 8
 9  #
10  # 

Re: Shell scripts: variable assignment within read loops

2008-08-17 Thread David Wolfskill
On Mon, Aug 18, 2008 at 06:29:03AM +0300, Giorgos Keramidas wrote:
 ...
 You are right that feeding data to a looping construct through a pipe
 may run in a subshell.  The ``Single UNIX Specification'' says
 

Ah; thanks for the confirmation.
 
...
 What I usually do in similar shell scripts is something like:
 
 cat ${filename} | sed -n -e '/foo/ s/bar/baz/' | \
 xargs -n1 blah
 
 This isn't exactly the same as assigning $foo to the results of the
 loop, but you can also use:
 
 foo=`cat $filename | while read bar ; do \
  stuff ...
  echo $bar
  more stuff...
  done`

Right; I had seen that type of construct in /etc/rc.d.* (which is where
I often look for samples of shell scripts that need to work reliably).

As you noticed, that won't quite do for what I'm trying to accomplish here.

 ...
  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)?
 
 Ah, that's much better.  Now I see what you are trying to do.

:-)

 Would you be ok with an awk(1) script instead of /bin/sh?  It tends
 to be nicer for this sort of thing, i.e.:

Yes, awk(1) would be OK.  I'll be more inclined to use it if I can
figure out a way to use it instead of sed(1) for a very different part
of the script.  :-}

 $ expand david.awk | cat -n
  1  #
  2  # Gather the field names if this is a header-line.
  3  #
  4  $0 ~ /^Name/ {
  5  for (k = 1; k = NF; k++)
  6  tag[k] = $k;
  7  }
  8
  9  #
 10  # For all other lines, just print the tagged field values.
 11  #
 12  $0 !~ /^Name/ {
 13  name = $1;
 14  for (k = 1; k = NF; k++) {
 15  if ($k == -)
 16  $k = 0;
 17  printf %s_%s: %s\n, tag[k], name, $k;
 18  }
 19  }
 
 $ netstat -nibd -f inet | awk -f david.awk
 Name_re0: re0
 Mtu_re0: 1500
 Network_re0: 192.168.1.0/2
 ...

Very cool; thank you very much!  I will study that a bit

(I'd normally do this stuff in Perl, but in addition to the other issues
mentioned earlier, the script will be sleeping most of the time, but
wake up  spit out results periodically.  The usual case will be every 5
minutes, but I plan to make use of it with radically shorter periods in
certain specialized environments -- such as every 5 seconds.  And I
still want it to be low overhead.  I also note in passing that in its
production environments, the script's standard output will be
redirected to append to a file on a different machine via an SSH tunnel.)

 With a bit of preprocessing, it may be possible to extract the network
 names and print the (end) NICs: XXX XXX part too.

Right -- much of the output I demonstrated was strictly for debugging/
expository purposes.

Thanks again, Giorgos!

Peace,
david
-- 
David H. Wolfskill  [EMAIL PROTECTED]
Depriving a girl or boy of an opportunity for education is evil.

See http://www.catwhisker.org/~david/publickey.gpg for my public key.


pgpQZk6NuCeRc.pgp
Description: PGP signature