Ed Morton
Ed Morton

Reputation: 204638

How do I populate a bash array with multi-line command output?

How can I populate a bash array with multi-line command output?

For example given this printf command:

$ printf 'a\nb\n\nc\n\nd\ne\nf\n\n'
a
b

c

d
e
f

I would like to have a bash array populated as if I wrote:

$ arr[0]='a
b'
$ arr[1]='c'
$ arr[2]='d
e
f'

and so could loop through it as:

$ for i in "${arr[@]}"; do printf "<%s>\n" "$i"; done
<a
b>
<c>
<d
e
f>

I have tried various incarnations of using a NUL character to separate my intended array fields instead of a blank line as that seems like my best bet but no luck so far, e.g.:

$ IFS=$'\0' declare -a arr="( $(printf 'a\nb\n\0c\n\0d\ne\nf\n\0') )"
$ for i in "${arr[@]}"; do printf "<%s>\n" "$i"; done
<a>
<b>
<c>
<d>
<e>
<f>

I also tried mapfile -d $'\0' but my mapfile doesn't support -d.

I did find that this works:

$ declare -a arr="( $(printf '"a\nb" "c" "d\ne\nf"') )"
$ for i in "${arr[@]}"; do printf "<%s>\n" "$i"; done
<a
b>
<c>
<d
e
f>

but that seems a little clunky and I'd have to escape "s when all I really want it to tell the shell to use some character other than a blank as the array fields separator.

Upvotes: 3

Views: 3395

Answers (1)

Charles Duffy
Charles Duffy

Reputation: 295904

A best-practice approach, using NUL delimiters:

arr=( )
while IFS= read -r -d '' item; do
  arr+=( "$item" )
done < <(printf 'a\nb\n\0c\n\0d\ne\nf\n\0')

...which would be even simpler with bash 4.4:

mapfile -t -d '' arr < <(printf 'a\nb\n\0c\n\0d\ne\nf\n\0')

Much more crudely, supporting the double-newline separator approach:

item=''
array=( )
while IFS= read -r line; do
  if [[ $line ]]; then
    if [[ $item ]]; then
      item+=$'\n'"$line"
    else
      item="$line"
    fi
  else
    [[ $item ]] && {
      array+=( "$item" )
      item=''
    }
  fi
done < <(printf 'a\nb\n\nc\n\nd\ne\nf\n\n')

Upvotes: 8

Related Questions