Search code examples
arraysbashmacos-mojaveifs

Mysteries IFS behavior


here https://stackoverflow.com/a/19915925/4673197 I learned I can split a string into array by setting IFS.

here https://stackoverflow.com/a/9429887 I learned I can join a array by IFS delimiter.

But in my test below:

0:~ $ a=(1 2 3)
0:~ $ echo "${a[*]}"
1 2 3
0:~ $ IFS=. echo "${a[*]}" # IFS=. not work
1 2 3
0:~ $ (IFS=.; echo "${a[*]}") # this works
1.2.3
0:~ $ echo $IFS # the original IFS is not change

0:~ $ v=1.2.3
0:~ $ IFS=. b=($v) # change string to array
0:~ $ echo ${b[*]}
1 2 3
0:~ $ echo "${b[*]}" # the array join by `.`!
1.2.3
0:~ $ echo ${b}
1
0:~ $ (IFS=,; echo "${b[*]}") # this still work
1,2,3
0:~ $ IFS=, echo "${b[*]}" # this not work, b array still join by .
1.2.3
0:~ $ c=(1 2 3)
0:~ $ echo "${c[*]}" # a new array join by '.' !
1.2.3
0:~ $ IFS=, echo "${c[*]}" # IFS=, not work, still join by '.'
1.2.3
0:~ $ (IFS=,; echo "${c[*]}") # this works
1,2,3
0:~ $ echo $IFS # original IFS is space

0:~ $ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

from the above, I have the following guessing/questions:

  1. IFS=. echo "${a[*]}" this change the echo environment, but not change the quoting expanding environment, so it's not work?
  2. if 1 is true, why IFS=. b=($v) successful create the array?
  3. why echo "${c[*]}" joined by .? the IFS should be default whitespace.

Solution

  • The problem is with the IFS setting when you do IFS=. b=($v) two shell assignments take place in the context of the current shell. The modification to IFS to . will make all subsequent array expansions of type "${arr[*]}" to make use of this new value. Note the double quotes here, same array expansion without the quotes won't use IFS i.e. ${arr[*]}

    So to answer your questions

    IFS=. echo "${a[*]}" this change the echo environment, but not change the quoting expanding environment, so it's not work?

    You are wrong, though you are passing IFS to the environment that echo receives. However, echo doesn't do anything with its environment list. The result happens because you've earlier set the value of IFS to .

    if 1 is true, why IFS=. b=($v) successful create the array?

    This command runs as if you were executing the two commands separately. Modify the IFS to . and expand the result of unquoted string v into the array which undergoes word-splitting with the value of IFS. Since you've defined it to be . previously, the string is split into individual components and stored into the array.

    why echo "${c[*]}" joined by .? the IFS should be default whitespace.

    It is already explained in the first answer. You've modified in the current shell to use IFS to be . and subsequent array expansions will take these for joining strings. If you start a new shell and run the same expansion it won't work.


    Just to clarify one more point on when passing a variable to the local environment of the command will work. If instead of

    var=val echo "$var"
    

    if you had written

    var=val sh -c 'echo "$var"'
    

    you see the value of var written, because in this case, you are passing the value of var in the environment, the sh shell is run. But unlike echo, sh reads from the environment and sees the value of var and uses it for variable expansion.