I have a script that I am returning multiple values from, each on a new line. To capture those values as bash variables I am using the read
builtin (as recommended here).
The problem is that when I use the new line character as the delimiter for read
, I seem to always get a non-zero exit code. This is playing havoc with the rest of my scripts, which check the result of the operation.
Here is a cut-down version of what I am doing:
$ read -d '\n' a b c < <(echo -e "1\n2\n3"); echo $?; echo $a $b $c
1
1 2 3
Notice the exit status of 1.
I don't want to rewrite my script (the echo
command above) to use a different delimiter (as it makes sense to use new lines in other places of the code).
How do I get read
to play nice and return a zero exit status when it successfully reads 3 values?
Update
Hmmm, it seems that I may be using the "delimiter" wrongly. From the man page:
-d *delim* The first character of delim is used to terminate the input line, rather than newline.
Therefore, one way I could achieve the desired result is to do this:
read -d '#' a b c < <(echo -e "1\n2\n3\n## END ##"); echo $?; echo $a $b $c
Perhaps there's a nicer way though?
The "problem" here is that read
returns non-zero when it reaches EOF
which happens when the delimiter isn't at the end of the input.
So adding a newline to the end of your input will make it work the way you expect (and fix the argument to -d
as indicated in gniourf_gniourf's comment).
What's happening in your example is that read
is scanning for \
and hitting EOF
before finding it. Then the input line is being split on \n
(because of IFS
) and assigned to $a
, $b
and $c
. Then read
is returning non-zero.
Using -d
for this is fine but \n
is the default delimiter so you aren't changing anything if you do that and if you had gotten the delimiter correct (-d $'\n'
) in the first place you would have seen your example not work at all (though it would have returned 0
from read). (See http://ideone.com/MWvgu7)
A common idiom when using read
(mostly with non-standard values for -d
is to test for read
's return value and whether the variable assigned to has a value. read -d '' line || [ "$line" ]
for example. Which works even when read fails on the last "line" of input because of a missing terminator at the end.
So to get your example working you want to either use multiple read
calls the way chepner indicated or (if you really want a single call) then you want (See http://ideone.com/xTL8Yn):
IFS=$'\n' read -d '' a b c < <(printf '1 1\n2 2\n3 3')
echo $?
printf '[%s]\n' "$a" "$b" "$c"
And adding \0
to the end of the input stream (e.g. printf '1 1\n2 2\n3 3\0'
) or putting || [ "$a" ]
at the end will avoid the failure return from the read
call.
The setting of IFS
for read is to prevent the shell from word-splitting on spaces and breaking up my input incorrectly. -d ''
is read
on \0
.
-d
is the wrong thing to use here. What you really want is three separate calls to read:
{ read a; read b; read c; } < <(echo $'1\n2\n3\n')
Be sure that the input ends with a newline so that the final read
has an exit status of 0.
If you don't know how many lines are in the input ahead of time, you need to read the values into an array. In bash
4, that takes just a single call to readarray
:
readarray -t arr < <(echo $'1\n2\n3\n')
Prior to bash
4, you need to use a loop:
while read value; do
arr+=("$value")
done < <(echo $'1\n2\n3\n')
read
always reads a single line of input; the -d
option changes read
's idea of what terminates a line. An example:
$ while read -d'#' value; do
> echo "$value"
> done << EOF
> a#b#c#
> EOF
a
b
c
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With