[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Stupid shell script question about "read" [SOLVED]

I wrote:

> Could someone tell me why the following works in zsh but not in
> bash/posh/dash?
> benjo[3]:~% echo foo bar baz | read a b c
> benjo[4]:~% echo $a $b $c
> foo bar baz

Thanks everyone for the enlightening answers!  So just to summarize, the
problem is that the pipeline is treated as a subshell, and so the
variables $a $b and $c are defined within the subshell but not the
"main" shell.

These seem like the best solutions to my problem:

* Bash-specific (i.e. not POSIX-compliant) :

David Kirchner wrote:

> I'm not sure of the POSIX way to use read in this manner, but I found
> this on Google/A9:
> http://linuxgazette.net/issue57/tag/1.html
> The example he gives, with the < <() syntax, worked in bash, but not
> in Debian or FreeBSD's /bin/sh.

Almut Behrens wrote:

> In more recent bashes, the following should work as well
> #!/bin/bash
> read a b c <<<`echo foo bar baz`
> echo $a $b $c
> The <<< ("here strings") are an extension of the "here document" syntax,
> IOW, the string given after <<< is supplied as stdin to the command.

* POSIX-compliant:

Bill Marcum wrote:

> I think the POSIX way would be
> echo foo bar baz | { read a b c; echo $a $b $c; }

Not too bad if what you want to do inside the { } braces is pretty short.

Almut Behrens wrote:

> #!/bin/sh
> eval `echo foo bar baz | (read a b c; echo "a='$a';b='$b';c='$c'" )`
> echo $a $b $c
> To get the variable's values from the subshell back to the main shell,
> a shell code fragment is written on stdout, captured with backticks,
> and then eval'ed in the main shell...  (this is the moment when I
> usually switch to some other scripting language -- if not before :)

Ugh.  Does get the job done though.  I guess one has to be a little
careful about escaping special characters in this case?  Here's the
safest version I've found so far -- single quotes in the input have to
be special cased with the sed command, and the -r flag to "read" keeps
it from eating backslashes.

	# set some variables to nightmarish values for testing purposes
	d='"ab\""q"' # literal value is "ab\""q"
	e='$d'       # literal value is $d
	f="'ba'r'"   # literal value is 'ba'r'

	# here's the meat of the code
	result="`echo "$d $e $f" | sed "s/'/\'\\\\\\\'\'/g" | \
		( read -r a b c; echo "a='$a' ; b='$b' ; c='$c'" )`"
	eval "$result"

	# test that $a $b $c have the right values
	echo "$a $b $c"

Tested on Sarge with zsh, bash, dash and posh :-)

Of course, replace this:
	echo "$d $e $f"
with whatever is producing the output that needs to be put into $a $b $c

Personally, I'd rather constrain my script to work only with bash and
use <<< or < <() operators than to write something like the above!
(N.B. every method above still needs the -r flag to read if the input
might contain backslashes.)

best regards,

Kevin B. McCarty <kmccarty@princeton.edu>   Physics Department
WWW: http://www.princeton.edu/~kmccarty/    Princeton University
GPG: public key ID 4F83C751                 Princeton, NJ 08544

Reply to: