bash string split by delimiter breaks on empty value

Viewed 69

I have a variable like LINE=foo,bar,,baz

I tried to split this with delimiter , using 2 different techniques:

  1. array=(${LINE//,/ })
  2. array=($(echo "$LINE" | tr ',' '\n'))

echo ${array[2]} should return an empty value but it returns baz (Both treat baz as the 3rd value when it should be the 4th instead.)

4 Answers

You can do this with read -a and an alternate delimiter:

IFS=, read -a array <<<"$LINE"

Note that since the assignment to IFS is a prefix to the read command, it only applies to that one command, and you don't have to set it back to normal afterward. Also, unlike the ones that depend on word-splitting of unquoted variables, it won't try to "expand" any entries that look like filename wildcards into lists of matching files.

Demo:

$ LINE=foo,bar,,baz
$ IFS=, read -a array <<<"$LINE"
$ declare -p array
declare -a array='([0]="foo" [1]="bar" [2]="" [3]="baz")'

You are relying on a sequence of whitespace between tokens as the separator, but of course, that will lose any empty strings.

As a somewhat crude workaround, temporarily override IFS:

oldIFS=$IFS
IFS=','
array=($LINE)
IFS=$oldIFS

Demo: https://ideone.com/Dd1gUV

By default array treat all 'blank' characters as delimiters. If you need to preserve empty values you have to take care of them first.

line='foo, ,bar,,baz'
line=${line//,,/,null,}
line=${line//,[[:blank:]],/,null,}
array=(${line//,/ })

$ echo "${array[@]}"
foo null bar null baz

$ echo "${array[@]//null/ }"
foo   bar   baz

You could use mapfile (or readarray, same thing):

$ LINE=foo,bar,,baz
$ declare -a PARTS
$ mapfile -t -d, PARTS <<<"$LINE"
$ declare -p PARTS
declare -a PARTS=([0]="foo" [1]="bar" [2]="" [3]=$'baz\n')

There's an extraneous newline at the end of the 3rd element, hence the $'baz\n' value, so you'd have to handle that (see discussion in comments, though). Not sure where it comes from.

Related