For completeness, in different shells:
Bourne shell
The Bourne shell from the late 70s was the shell that introduced command substitution. It's no longer in use nowadays but a number of shells have their syntax based on that of that shell.
var=`any shell code`
Interprets the any shell code
in a child process where the output is then going to a pipe. Meanwhile, the shell reads that output from the other end of the pipe and stores it in the $var
variable.
Note however that all the trailing newline characters in that output are removed.
The Bourne shell had no array variable support, but you could store the words of the output of commands into the positional parameters ($1
, $2
...) with:
set -- `some code`
Or:
set x `some code`; shift
In earlier versions where --
was not supported.
The output of some code
, beside the trailing newline stripping was also subject to $IFS
-based splitting + globbing. For instance, if some code outputs foo,/*<newline><newline>
and $IFS
contains ,
, $1
will contain /foo
and the remaining arguments are all the non-hidden files in /
.
csh
Also from the late 70s, early 80s, very popular at the time, still survives as tcsh
on some systems despite its many shortcomings.
set array = `some code`
stores the words resulting of the splitting of the output of some code
(on space, tab or newline) in the $array
list variable.
set array = "`some code`"
Same except the splitting is done on newlines only (the elements of $array
will be the non-empty lines of the output).
ksh88 and POSIX like shells.
These days, sh
is one or other implementation of an interpreter for the standard sh language, which itself is mostly based on a subset of ksh88's, ksh88 being the last evolution of a shell written by David Korn, derived from the Bourne shell with some fixes and enhancements. Among those, there are bash, dash, ksh, yash, mksh, zsh (though for many of those, compliance to the sh specification has to be enabled via some option, or by calling them as sh
).
var=$(any shell code)
It works like the Bourne shell's var=`...`
except it makes nesting them a lot easier, and doesn't mess up the interpretation of backslashes within.
Though ksh had arrays, array support was not included in the POSIX sh specification.
rc and derivatives
rc
was the shell of Research Unix V10 in the late 80s and plan9, the once to be successor of Unix. There also exists a public domain clone of that shell which spawned a few derivatives: es
and akanga
at least. It's got a much better syntax than the ones above, but never really took off unfortunately.
array = `cmd
array = ` {any shell code}
Stores the output of any shell code
, split on the characters of $ifs
into the $array
.
array = `` (chars) {any shell code}
Same except that the specified chars
are used instead of $ifs
to do the splitting. With var = ``(){shell code}
, the output is stored exactly with no splitting (the only shell that can actually do that out of the box).
ksh
ksh was the first Bourne-like shell to introduce arrays in the early 80s. Syntax was:
set -A array -- $(some code)
Like in the Bourne shell, it does trailing newline stripping, $IFS
-splitting and globbing.
some command | read var1 var2 var3
Can also be used to read the first line of the output of some command
, split it on $IFS
(but where backslash can be used to escape the separators and newline) and store the result in those variables.
It also works in zsh, but not in some other ksh-like shells such as bash, yash or pdksh/mksh where that read
is run in a subshell (for sh
, the standard leaves it unspecified whether than happens or not, so you can't use that portably in sh
). In bash, shopt -s lastpipe
helps in non-interactive invocations of the shell.
some code | read -A array
Same except the words of that first line are stored in the array elements (bash
renamed the option to -a
for its own read
builtin).
ksh also introduced co-processes.
some command |&
IFS= read -r first_line <&p
IFS= read -r second_line <&p
Could be used for instance to read the first and second line from the output of some command
into respective variables.
zsh and bash also added co-process support later with different syntax.
zsh
zsh (another Bourne-like shell but also with features from csh and rc) introduced array support in version 2.0 in 1991 (a few months after its first version was released the previous year).
array=( $(some code) )
Would do newline stripping and IFS-splitting (not globbing) and assign the resulting words to the elements of the array.
That syntax was later copied by ksh93 in 1993 and bash 2.0 in 1996, though they do globbing on top like ksh88.
To split with things other than $IFS
in zsh
:
array=( ${(f)"$(some code)"} ) # split on lineFeed aka newline
array=( ${(0)"$(some code)"} ) # split on NUL
array=( ${(s[string])"$(some code)"} ) # split on any string
zsh
is also the only shell that can store NUL characters in its variables, so the only shell that doesn't choke on the output of commands that contain such characters. NUL is actually in the default value of $IFS
in zsh
(in addition of space, tab and newline like in the other Bourne-like shells).
some command | IFS= read -rd '' var
Can be used to store the output of some command
up to the first NUL character into $var
. Since NUL cannot occur in text, that's a way to store text as is. -d
is from ksh93, but -d ''
to delimit on NULs is a bash/zsh addition.
zsh also added some more options to read
such as -k
to read arbitrary numbers of characters (was initially meant to read key presses, but can be used for arbitrary characters when combined with -u
to specify the fd to read from). ksh93
and later bash
added -N
/-n
options with related semantics:
some command | read -u0 -k10 var
Stores the first 10 characters of the output of some command
in $var
.
A sysread
building in the zsh/system
module can also be used to read input in blocks as a raw'er interface to the read()
system call.
A zpty
builtin from the eponymous module can also be used to interact with a command via a pseudo-terminal pair, allowing you to send input and read its output in a more interactive fashion like expect
does.
ksh93
A complete rewrite of ksh by its author released in late 1993 and which has heavily inspired bash 2+ (and to some extent zsh and mksh)
var=${
any shell code
}
That's the same as var=$(any shell code)
except any shell code
doesn't run in a subshell environment, which means it's slightly more efficient as the shell doesn't need to same and restore the environment after and means, modifications that you do inside persist afterwards.
It's also supported by recent versions of mksh
.
bash
readarray array < <(some command)
stores the lines of some command
(including their delimiter which you can remove with the -t
option) in the elements of the $array
array. <(...)
called process substitution was added to ksh in the mid 80s, but couldn't be used as the target of a redirection initially (was addressed in ksh93 much later).
With bash 4.4+, you can use different delimiters than newline including NUL:
readarray -t -d '' array < <(find . -print0)
can be used for instance to store the contents of the NUL-delimited records in the output of find
into the $array
.
You can also do:
read var1 var2 < <(some command)
Which works even when the lastpipe
option is not enabled (also works in zsh).
yash
read var1 var2 <(any shell code)
Though it looks like ksh's process subsitution, the feature there is called process redirection, and does not expand to the path of some named pipe, but assigns the corresponding fd (here 0) to the reading end of a pipe whose other end is connected to the output of the command.
fish
A relative late comer with a significantly different syntax from other shells (often much better, though still of a moving target):
set array (some shell code)
Assigns the contents of each line of the output of the shell code to elements of the array.
some command | read var1 var2
Would read the first line, split on $IFS
(though will change) or arbitrary delimiters with -d delim
and stores in $var1
, $var2
. read -a array
for the words of that line to be stored in an array.
some command | read -L var1 var2
For the first two lines in different variables.
some command | read -z var
For everything up to the first NUL (so everything for text output) to be stored in $var
.