1

This question already has an answer here:

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

marked as duplicate by muru, Jeff Schaller, sebasth, Shadur, Kiwy Sep 11 '18 at 12:57

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 2
    for me, it doesn't work in bash either. Also, please clarify if you expect an answer for sh or dash. – pLumo Sep 11 '18 at 7:06
3

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:

  • 2
    for me, it is not working in bash (4.3.48). – pLumo Sep 11 '18 at 6:53
  • Thanks, I made it clear that this answer is for sh. I don't use dash, so someone else might help. – pLumo Sep 11 '18 at 6:58
  • 1
    "in Bash < 4.4"? Is there some reason to think that would work differently in Bash 4.4 or later? – ilkkachu Sep 11 '18 at 8:16
  • 1
    @Stephen Kitt said earlier in a now deleted comment that the command works for him in 4.4.x. Also, OP reports that it works for him in bash. Unfortunately, I did not find sources for that... – pLumo Sep 11 '18 at 8:19
  • 1
    I’m not sure what I was smoking earlier, the initial command doesn’t work for me on Bash 4.4 either now (and there’s nothing in the changes from 4.3 to 4.4 which would explain the difference in behaviour). – Stephen Kitt Sep 11 '18 at 8:47
2

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
0

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:

  1. here-string (works in bash, ksh, zsh):

    $ bash -c 'read X Y <<<"AAA BBB"; echo "<$X>" '
    <AAA>
    
  2. 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:

  1. 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>
    
  • Presumably, the OP is using the lastpipe option in bash. Try bash -O lastpipe -c 'echo AAA BBB | read X Y; echo "<$X>"' – Stéphane Chazelas Sep 11 '18 at 9:50
  • @StéphaneChazelas Yes, the user may use (may be using) lastpipe, but I didn't read any confirmation of that. Details added anyway. – Isaac Sep 11 '18 at 10:01
-3

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.