8

I'm going through a Bash tutorial, and specifically the subject of word splitting.

This script, called "args", helps demonstrate word splitting examples:

#!/usr/bin/env bash
printf "%d args:" $#
printf " <%s>" "$@"
echo

An example:

$ ./args hello world, here is "a string of text!"
5 args: <hello> <world,> <here> <is> <a string of text!>

So far so good. I understand how this works.

However, when I replace IFS with a non-whitespace character, say :, the script does not perform word splitting if I pass the string directly as an argument.

$ ./args one:two:three
1 args: <one:two:three>

However, the script does perform word splitting on the same string if I (1) assign the string to a variable, and then (2) pass the string to the script via parameter expansion.

$ IFS=:
$ variable="one:two:three"
$ ./args $variable
3 args: <one> <two> <three>

Why? Specifically, why does passing the string as an argument undergo word splitting when IFS is unset and the delimiters are whitespace characters, but not when IFS is set to non-whitespace characters?

When I use read instead of this script, the same string also undergoes word splitting as expected.

$ IFS=:
$ read a b c
one:two:three
$ echo $a $b $c
one two three
codeforester
  • 39,467
  • 16
  • 112
  • 140
Nadim Hussami
  • 349
  • 2
  • 14

1 Answers1

11

You can read more about word splitting here.

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

When you pass the bare string one:two:three as an argument with IFS set to :, Bash doesn't do word splitting because the bare string is not one of parameter expansion, command substitution, or arithmetic expansion contexts.

However, when the same string is assigned to a variable and the variable is passed to the script unquoted, word splitting does occur as it is a case of parameter expansion.

The same thing applies to these as well (command substitution):

$ ./args $(echo one:two:three)
3 args: <one> <two> <three>

$ ./args "$(echo one:two:three)"
1 args: <one:two:three>

As documented, read command does do word splitting on every line read, unless IFS has been set to an empty string.


codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 1
    Good job, you nailed that one `:)` – David C. Rankin Apr 02 '17 at 02:55
  • I understand that parameter expansion will be followed by word splitting, as per the rules of the order of shell expansion as detailed in gnu.org. Furthermore, in the first link you sent me, the last line reads "Note that if no expansion occurs, no splitting is performed." Got it. So then why _is_ word splitting triggered at all in this script when I execute `./args hello world, here is "a string of text!"` ? I haven't explicitly performed parameter expansion there, yet word splitting is performed, which means something triggered expansion. – Nadim Hussami Apr 02 '17 at 08:16
  • 1
    The only thing I'm seeing is that a bare string with IFS set to the default whitespace characters triggers word splitting, but a bare string with IFS set to a non-whitespace character does not unless it is called explicitly by a parameter expansion, command substitution or similar expansion. My guess is this has to do more with the printf builtin, and my lack of understanding on how _that_ works. You basically said as much when you said "As documented, `read` command does do word splitting on every line read, unless IFS has been set to an empty string. Seems printf does things differently. – Nadim Hussami Apr 02 '17 at 08:24
  • 2
    Regular arguments are processed as a list of tokens separated by white space, and hence there is no IFS at play in your printf. – codeforester Apr 02 '17 at 20:26
  • 1
    Perfect, thanks! This last comment was the missing information that answered my intended question. – Nadim Hussami Apr 03 '17 at 08:19