In bash
or zsh
, I can use following syntax to read from pipe into variables:
echo AAA BBB | read X Y ; echo $X
which will print AAA
Why does the same not work in /bin/sh
?
I am using /bin/sh
-> /bin/dash
dash in Debian
In bash
or zsh
, I can use following syntax to read from pipe into variables:
echo AAA BBB | read X Y ; echo $X
which will print AAA
Why does the same not work in /bin/sh
?
I am using /bin/sh
-> /bin/dash
dash in Debian
Why does the same not work in '/bin/sh' ?
Assigning variables in a pipe does not work as expected in sh
and bash
because each command of a pipe runs in a subshell. Actually, the command does work, X
and Y
get declared, but they are not available outside the pipe.
The following will work:
echo AAA BBB | { read X Y ; echo $X; }
But in your case:
try this,
read X Y <<< "AAA BBB"
or
read X Y < <(echo "AAA BBB")
Some useful links:
sh
. I don't use dash
, so someone else might help.
– pLumo
Sep 11 '18 at 06:58
sh
, which made no difference in any case.
– JdeBP
Sep 11 '18 at 08:41
read X Y < <(echo "AAA BBB")
will not work with dash
that ships with Debian and is symlinked from sh
, but using << EOF ...
. The here doc method seems to be the only POSIX compliant solution that allows to pipe in strings across the board.
– Martin Braun
Feb 23 '23 at 23:10
in bash or zsh, I can use following syntax to read from pipe into variables.
echo AAA BBB | read X Y ; echo $X
No, you can't. Not in Bash with the default settings.
$ ./bash5.0-alpha -c 'echo AAA BBB | read X Y ; echo "Bash=$BASH_VERSION X=\"$X\""'
Bash=5.0.0(1)-alpha X=""
$ bash -c 'echo AAA BBB | read X Y ; echo "Bash=$BASH_VERSION X=\"$X\""'
Bash=4.4.12(1)-release X=""
Bash runs all the commands in a pipeline in separate subshell environments, so the changes to shell variables aren't visible outside the pipeline. Dash is similar here.
Zsh and ksh (AT&T implementations, not pdksh
or derivatives) run the last command of the pipeline in the main shell environment, so there that works:
$ zsh -c 'echo AAA BBB | read X Y ; echo "Zsh=$ZSH_VERSION X=\"$X\""'
Zsh=5.3.1 X="AAA"
In Bash, you can shopt -s lastpipe
to have it do what ksh and zsh do (only works in non-interactive shells though):
$ bash -O lastpipe -c 'echo AAA BBB | read X Y ; echo "Bash=$BASH_VERSION X=\"$X\""'
Bash=4.4.12(1)-release X="AAA"
But I don't think there's such an option for Dash.
In Bash you could also use process substitution instead of the pipe, but that's not an option in Dash either.
The workarounds would revolve around making the right-hand side of the loop a compound statement or a function, and so using the value read from the pipe in the same environment it was read in.
$ dash -c 'echo AAA BBB | { read X Y ; echo "X=\"$X\""; } '
X="AAA"
$ dash -c 'f() { read X Y ; echo "X=\"$X\""; }; echo AAA BBB | f'
X="AAA"
Or use a here document:
read X Y << EOF
$(echo AAA BBB)
EOF
No, this does not set X and Y (after the semicolon).
echo AAA BBB | read X Y ; echo $X
$ bash -c 'echo AAA BBB | read X Y ; echo "<$X>"'
<>
The same happens in dash.
To get it to work in dash you need to resort to older solutions (here-doc):
$ read X Y <<_EOT_
> AAA BBB
> _EOT_
$ echo "<$X>"
<AAA>
As a command to try shells (sadly the newlines can not be removed (no one-liner)):
$ bash -c 'read X Y <<_EOT_
AAA BBB
_EOT_
echo "<$X>" '
<AAA>
That works exactly the same in dash, zsh, ksh, and many others:
$ dash -c 'read X Y <<_EOT_
AAA BBB
_EOT_
echo "<$X>" '
<AAA>
Newer alternatives (that do not work in dash) to the here-doc
may be:
here-string (works in bash, ksh, zsh):
$ bash -c 'read X Y <<<"AAA BBB"; echo "<$X>" '
<AAA>
Process substitution (works in bash, ksh, zsh):
$ bash -c 'read X Y < <(echo AAA BBB) ; echo "<$X>" '
<AAA>
An alternative that prints the value and works in dash, but does not keep the variable set in dash or bash (but does in ksh and zsh), is:
Group command:
$ dash -c 'echo "AAA BBB" | { read X Y ; echo "<$X>"; }; echo "<$X>" '
<AAA>
<>
Note that this last solution may be set to keep the variables set in bash with the lastpipe
option (not for interactive use) or in ksh/zsh by default:
$ bash -c 'shopt -s lastpipe;echo "AAA BBB"|{ read X Y;echo "1<$X>"; };echo "2<$X>"'
1<AAA>
2<AAA>
$ ksh -c 'echo "AAA BBB"|{ read X Y;echo "1<$X>"; };echo "2<$X>"'
1<AAA>
2<AAA>
$ zsh -c 'echo "AAA BBB"|{ read X Y;echo "1<$X>"; }; echo "2<$X>"'
1<AAA>
2<AAA>
lastpipe
option in bash
. Try bash -O lastpipe -c 'echo AAA BBB | read X Y; echo "<$X>"'
– Stéphane Chazelas
Sep 11 '18 at 09:50
lastpipe
, but I didn't read any confirmation of that. Details added anyway.
–
Sep 11 '18 at 10:01
Be careful with variable assignments from a process in a pipeline. The POSIX standard does not require a specific behavior.
Modern shells like ksh93
and recent versions of the Bourne Shell
let the main shell be the parent of both processes in your pipeline and in case the rightmost process is a builtin command, this command is even run in the main shell.
Another variant is to use the above method but to always run the rightmost command in another process.
The old version is how the original Bourne Shell worked: The shell forks and the forked process creates all other proceses from the pipe and finally converts into the rightmost process.
The last version needs a lot less code than the others but is slower. Because of the code size, this was used in 1976.
The first variant is the fastest variant but needs more code than the others, but it it the only variant that runs the variable assignment in the orginal shell process, which is required to have the modified variable value in the main shell.
bash
either. Also, please clarify if you expect an answer forsh
ordash
. – pLumo Sep 11 '18 at 07:06