8

I've been reading up on this post: bash "for in" looping on null delimited string variable to see if I would be able to handle arbitrary text containing spaces inside an array.

Based on the post above this works fine:

while IFS= read -r -d '' myvar; do echo $myvar; done < <(find . -type f -print0)

To check my understanding I also did this (which still works fine):

while IFS= read -r -d '' myvar; do echo $myvar; done < <(printf "%s\0" 'a b' 'c d')

However, then I attempt storing the output in an array it goes wrong:

IFS= read -r -d '' -a myvar < <(printf "%s\0" 'a b' 'c d')

The array holds only a b and not c d:

echo ${myvar[@]}
a b

Apparently, there is a finer detail I am missing here. Thanks for any help.

PS. I am running:

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14)
Copyright (C) 2007 Free Software Foundation, Inc.
Community
  • 1
  • 1
Tore H-W
  • 181
  • 4
  • 2
    From `help read`: *Read a line from the standard input and split it into fields.* 'a b\0c d` is more than a/one line. – Cyrus Jan 01 '16 at 10:24
  • 3
    `read -a` is for assigning words on a single line into an array. To read null-delimited lines into an array: `unset arr i; while IFS= read -r -d '' 'arr[i++]'; do :; done < <(printf "%s\0" 'a b' 'c d')`. – 4ae1e1 Jan 01 '16 at 10:26
  • And a line is by definition terminated by ascii 0, right? Newline, for instance, is just a formatting character? – Tore H-W Jan 01 '16 at 11:17
  • This use case makes me wonder why the `readarray` command doesn't allow you to specify an alternate line terminator. – chepner Jan 01 '16 at 14:29
  • I've implemented @4ae1e1's suggestion and it works fine. The compact syntax `read 'arr[i++]'; do :; done` was new to me. Thanks. I side effect was that I also learned the difference between: – Tore H-W Jan 02 '16 at 00:26
  • ... I also realised that `arr1=(find *.*)` is not the same as `arr2=(*.*)`. The length of `arr1` is 1+ bigger than `arr2`. – Tore H-W Jan 02 '16 at 00:37

1 Answers1

13

In bash 4.4, the readarray command gained a -d option analogous to the same option for read.

$ IFS= readarray -d '' myvar < <(printf "%s\0" 'a b' 'c d')
$ printf "%s\n" "${myvar[@]}"
a b
c d

If you must support earlier versions, you need to loop over the output explicitly.

while IFS= read -d '' line; do
    myvar+=( "$line" )
done < <(printf "%s\0" 'a b' 'c d')
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Good to know `readarray` is finally getting `-d`. That's really a natural option to expect. – 4ae1e1 Jan 01 '16 at 23:07
  • `myvar+=( "$line" )` splits on space character – Tore H-W Jan 02 '16 at 00:42
  • 1
    @ToreH-W Not when `$line` is quoted. – chepner Jan 02 '16 at 01:29
  • 1
    I just learned another neat trick from this answer, which is that the `printf` command automatically loops as long as there are unused arguments remaining (and fabricates empty strings as arguments if there are not enough to fill the template). – 200_success Dec 25 '20 at 11:36