In old versions of bash
you had to quote variables after <<<
. That was fixed in 4.4. In older versions, the variable would be split on IFS and the resulting words joined on space before being stored in the temporary file that makes up that <<<
redirection.
In 4.2 and before, when redirecting builtins like read
or command
, that splitting would even take the IFS for that builtin (4.3 fixed that):
$ bash-4.2 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo "$x"'
a b c d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. cat <<< $a'
a.b.c.d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. command cat <<< $a'
a b c d
That one fixed in 4.3:
$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo "$x"'
a.b.c.d
But $a
is still subject to word splitting there:
$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo "$x"'
a b c d
In 4.4:
$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo "$x"'
a.b.c.d
For portability to older versions, quote your variable (or use zsh
where that <<<
comes from in the first place and that doesn't have that issue)
$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d
Note that that approach to split a string only works for strings that don't contain newline characters. Also note that a..b.c.
would be split into "a"
, ""
, "b"
, "c"
(no empty last element).
To split arbitrary strings you can use the split+glob operator instead (which would make it standard and avoid storing the content of a variable in a temp file as <<<
does):
var='a.new
line..b.c.'
set -o noglob # disable glob
IFS=.
set -- $var'' # split+glob
for i do
printf 'item: <%s>\n' "$i"
done
or:
array=($var'') # in shells with array support
The ''
is to preserve a trailing empty element if any. That would also split an empty $var
into one empty element.
Or use a shell with a proper splitting operator:
zsh
:
array=(${(s:.:)var} # removes empty elements
array=("${(@s:.:)var}") # preserves empty elements
rc
:
array = ``(.){printf %s $var} # removes empty elements
fish
set array (string split . -- $var) # not for multiline $var