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 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-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 

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-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-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


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 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