Bash IFS causes unexected results with array output

93 views Asked by At

When IFS has its default value and an array is printed without quotes, the interpreted value doesn't have quotes, but when IFS doesn't have a space it it, it does.

Using an echo web server and curl to demonstrate why this makes a difference:

bash-5.2$ echo $BASH_VERSION 
+ echo '5.2.21(1)-release'
5.2.21(1)-release
bash-5.2$ declare -a testit=([0]="-H" [1]="foo: bar")
+ testit=(['0']='-H' ['1']='foo: bar')
+ declare -a testit
bash-5.2$ declare -p testit
+ declare -p testit
declare -a testit=([0]="-H" [1]="foo: bar")
bash-5.2$ IFS=$' \t\n'
+ IFS='         
'
bash-5.2$ declare -p IFS
+ declare -p IFS
declare -- IFS=$' \t\n'
bash-5.2$ echo ${testit[@]}
+ echo -H foo: bar
-H foo: bar
bash-5.2$ curl [::1] ${testit[@]}
+ curl '[::1]' -H foo: bar
GET / HTTP/1.1
Host: [::1]
User-Agent: curl/8.5.0
Accept: */*

curl: (6) Could not resolve host: bar
bash-5.2$ echo "${testit[@]}"
+ echo -H 'foo: bar'
-H foo: bar
bash-5.2$ curl [::1] "${testit[@]}"
+ curl '[::1]' -H 'foo: bar'
GET / HTTP/1.1
Host: [::1]
User-Agent: curl/8.5.0
Accept: */*
foo: bar

bash-5.2$ IFS=$'_\t\n'
+ IFS='_        
'
bash-5.2$ declare -p IFS
+ declare -p IFS
declare -- IFS=$'_\t\n'
bash-5.2$ echo ${testit[@]}
+ echo -H 'foo: bar'
-H foo: bar
bash-5.2$ curl [::1] ${testit[@]}
+ curl '[::1]' -H 'foo: bar'
GET / HTTP/1.1
Host: [::1]
User-Agent: curl/8.5.0
Accept: */*
foo: bar

bash-5.2$ echo "${testit[@]}"
+ echo -H 'foo: bar'
-H foo: bar
bash-5.2$ curl [::1] "${testit[@]}"
+ curl '[::1]' -H 'foo: bar'
GET / HTTP/1.1
Host: [::1]
User-Agent: curl/8.5.0
Accept: */*
foo: bar

Why does the removal of the space from IFS (regardless of where you put the space, and regardless of whether you replace the space with another character; underscore, for example) cause this different behavior? The only value of IFS on output is the first character, and it doesn't matter where the space is to cause the unexpected behavior.

2

There are 2 answers

1
tjm3772 On BEST ANSWER

IFS is used to split the result of a parameter expansion, per Word Splitting in the manual:

The shell scans the results of parameter expansion, command substitution, 
and arithmetic expansion that did not occur within double quotes for word splitting.

The shell treats each character of $IFS as a delimiter, and splits 
the results of the other expansions into words using these characters 
as field terminators.

When IFS contains a space, the unquoted expansion foo: bar gets split into the two words foo: and bar. When you remove the space from IFS this splitting does not occur, so the debug output displays 'foo: bar' in quotes to indicate that this is a single word on the command line and not two words like the space might otherwise indicate. You would also get the quotes in the debug output if IFS contains a space but you quote the variable expansion to disable word splitting.

user@host$ var='foo: bar'
user@host$ set -x
user@host$ echo $var
+ echo foo: bar
foo: bar
user@host$ echo "$var"
+ echo 'foo: bar'
foo: bar
user@host$ declare -p IFS
+ declare -p IFS
declare -- IFS="    
"
2
glenn jackman On

IFS only affects arrays when you use * as the index, and you put the parameter expansion in double quotes:

$ a=(foo bar baz)
$ IFS=":"
$ printf '%s\n' ${a[@]}
foo
bar
baz
$ printf '%s\n' ${a[*]}
foo
bar
baz
$ printf '%s\n' "${a[@]}"
foo
bar
baz
$ printf '%s\n' "${a[*]}"
foo:bar:baz