bash: Assign variable from pipe?

To complement Charles Duffy's helpful answer with a focus on making it work in bash:

By default, and on Bash v4.1- invariably, any variable creations / modifications in a (multi-segment) pipeline happen in a subshell, so that the result will not be visible to the calling shell.

In Bash v4.2+, you can set option lastpipe to make the last pipeline segment run in the current shell, so that variable creations/modifications made there are visible to it.

For that to work in an interactive shell, you must additionally turn off job control with set +m.

Here's a complete example (Bash v4.2+):

$ unset x; shopt -s lastpipe; set +m; seq 3 | x=$(cat); echo "$x"
1
2
3

That said,

x=$(seq 3)

(the modern equivalent of your x=`seq 3`) is much simpler - it is POSIX-compliant and therefore works on older Bash versions too, and it requires no fiddling with global options.


This is covered in detail in BashFAQ #24.

You can reliably use a variable collected on the right-hand side of a pipeline only if the code referencing it is also on the right-hand side of that pipeline.

#!/bin/bash
echo "hello" | { read -r var; echo "Read value: $var"; }
echo "After the pipeline exited, var contains: $var"

Typical output is:

Read value: hello
After the pipeline exited, var contains:

The POSIX sh specification neither requires nor precludes the right-hand side of a pipeline being executed in the same shell which later executes subsequent commands. Thus, a shell may execute the read on the second line in the same shell where it executes the echo on the third -- but bash, specifically, will not do so unless the lastpipe shell option is enabled.


piped input

The simplest method to set a variable is to read it:

seq 3 | read -d '' x

Will also work in zsh but not in ksh (no NUL allowed for the -d option in ksh).

One way to use the value of x is to do so in the subshell created by the pipe:

$ seq 3 | { read -d '' x; echo "$x"; }
1
2
3

Note that the exit condition of read is of failure (because no '' character was found). More details could be found in Bash FAQ 24

In ksh and zsh the value could be used after the pipe has ended (using a char that is not in the input (but not NUL) to make ksh also work):

$ seq 3 | read -d ':' x
$ echo "$x"
1
2
3

That's because, originally in ksh (later in zsh) the last command on a pipe would run in the same process as the parent shell, keeping the value of the variable changed after the pipe has ended.

One way to emulate this capacity in bash is to use lastpipe (which will only work if JOB CONTROL is disabled (inside a non-interactive script or with set +m)):

$ set +m; shopt -s lastpipe
$ seq 3 | read -d '' x
$ echo "$x"
1
2
3

Capture command output

Using Process Substitution :

$ read -d '' x < <(seq 3)
$ echo "$x"
1
2
3

Or, using the old here-doc:

$ read -d '' x <<-Hello
> $(seq 3)
> Hello
$ echo "$x"
1
2
3

Or, using printf:

$ printf -v x "$(seq 3)"
$ echo "$x"
1
2
3

Tags:

Bash