32

I stuck with an strange behaviour of readarray command.

The man bash states:

readarray
     Read lines from the standard input into the indexed array variable array

but these scripts don't work (array is empty):

unset arr; (echo a; echo b; echo c) | readarray arr; echo ${#arr[@]}
unset arr; cat /etc/passwd | readarray arr;  echo ${#arr[@]}

And these work:

unset arr; readarray arr < /etc/passwd ;  echo ${#arr[@]}
unset arr; mkfifo /tmp/fifo; (echo a; echo b; echo c) > /tmp/fifo & mapfile arr < /tmp/fifo ; echo ${#arr[@]}

What wrong with pipe?

dchirikov
  • 3,888

3 Answers3

43

To ensure the readarray command executes in the current shell, either use process substitution in place of the pipeline:

readarray -t arr < <( echo a; echo b; echo c )

or (if bash 4.2 or later) use the lastpipe shell option:

shopt -s lastpipe
( echo a; echo b; echo c ) | readarray -t arr

Note that this second method using lastpipe will not work by default in an interactive session. In that case, first run

set +m

to disable "monitor mode".

chepner
  • 7,501
  • 3
    Cool. This works, but what exactly is process substitution? And what does it mean to have < < 2 arrows? – CMCDragonkai Jul 01 '14 at 02:15
  • 6
    See the bash man page. In short, it's syntax for treating a pipeline as a file descriptor. < <(...) means to redirect input (the first <) from the output of the command inside <(...). Similary, > >(...) would pass standard output to the standard input of the pipeline inside >(...). You don't necessarily need to use redirection with process substitution. cat <( echo a b c ) works as well. – chepner Jul 01 '14 at 14:40
  • Both of these options produce an undesirable result for me, where each array item retains the line endings at the end of each string. Whereas the answer by smac89 does not have this problem. – thnee Feb 01 '19 at 16:13
  • 1
    Line endings can be stripped using readarray -t arr. From man bash: -t Remove a trailing newline from each line read. – JK Laiho Jan 28 '20 at 11:10
  • I'm using bash 5.0.17 and this doesn't work for me. I've even checked BASHOPTS and it contains lastpipe, yet cat myfile.txt | readarray -t arr gives an empty arr, but readarray -t arr < <(cat myfile.txt) works correctly. – Brandon Miller Dec 25 '20 at 17:27
  • Ok, it seems that it works in a script file, but not on the command line. I don't know why that is. – Brandon Miller Dec 25 '20 at 17:57
  • 1
    @BrandonMiller, this is due to monitor mode and can be disabled with set +m. It is on by default in interactive sessions, but not in scripts. – Stanley Yu Feb 20 '22 at 22:45
17

Maybe try:

unset arr
printf %s\\n a b c | {
    readarray arr
    echo ${#arr[@]}
}

I expect it will work, but the moment you step out of that last { shell ; } context at the end of the |pipeline there you'll lose your variable value. This is because each of the |separate | processes within a |pipeline is executed in a (subshell). So your thing doesn't work for the same reason:

( arr=( a b c ) ) ; echo ${arr[@]}

...doesn't - the variable value was set in a different shell process than the one in which you call on it.

mikeserv
  • 58,310
5

readarray can also read from stdin, so:

readarray arr <<< "$(echo a; echo b; echo c)"; echo ${#arr[@]}
smac89
  • 1,443