Search code examples
arraysbashcurlechoifs

Bash IFS causes unexected results with array output


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.


Solution

  • 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="    
    "