The shell's parser performs several operations on your commands before finally executing them. Understanding how your original command will be transformed by the shell is of paramount importance in writing robust scripts. From the bash man page:

This page will focus on word splitting, of course. Before we get into the technical details, let's write a little helper script that will show us the arguments as passed by the shell:

#!/bin/sh
printf "$# args: "
for i; do
  printf "'%s' " "$i"
done
echo

griffon:~$ args hello world "how are you?"
3 args: 'hello' 'world' 'how are you?' 

The ultimate result of most shell commands is to execute some program with a specific set of arguments (as well as setting up environment variables, opening file descriptors, etc.). Word splitting is part of the process that determines what each of those arguments will be -- after word splitting and pathname expansion, every resulting word becomes an argument to the program that the shell executes. Our helper program above receives the argument list as constructed by the shell, and shows it to us.

Word splitting is performed on the results of almost all unquoted expansions. The result of the expansion is broken into separate words based on the characters of the IFS variable. If IFS is not set, then it will be performed as if IFS contained a space, a tab, and a newline. For example:

griffon:~$ var="This is a variable"
griffon:~$ args $var
4 args: 'This' 'is' 'a' 'variable' 

An example using IFS:

griffon:~$ log=/var/log/qmail/current IFS=/
griffon:~$ args $log
5 args: '' 'var' 'log' 'qmail' 'current' 
griffon:~$ unset IFS

An example with CommandSubstitution:

griffon:/music/Yello$ ls -l
total 2864
-rw-r--r-- 1 greg greg 2919154 2001-05-23 00:48 Yello - Oh Yeah.mp3
griffon:/music/Yello$ args $(ls)
4 args: 'Yello' '-' 'Oh' 'Yeah.mp3' 

As you can see above, we usually do not want to let word splitting occur when filenames are involved. (See BashPitfalls for a discussion of this particular issue.)

Double quoting an expansion suppresses word splitting, except in the special cases of "$@" and "${array[@]}":

griffon:~$ var="This is a variable"; args "$var"
1 args: 'This is a variable' 
griffon:~$ array=(testing, testing, "1 2 3"); args "${array[@]}"
3 args: 'testing,' 'testing,' '1 2 3' 

"$@" causes each positional parameter to be expanded to a separate word; its array equivalent likewise causes each element of the array to be expanded to a separate word.

There are very complicated rules involving whitespace characters in IFS. Quoting the man page again:

We won't explore those rules in depth here, except to note the part about sequences of non-whitespace characters. If IFS contains non-whitespace characters, then empty words can be generated:

griffon:~$ getent passwd sshd
sshd:x:100:65534::/var/run/sshd:/usr/sbin/nologin
griffon:~$ IFS=:; args $(getent passwd sshd)
7 args: 'sshd' 'x' '100' '65534' '' '/var/run/sshd' '/usr/sbin/nologin' 
griffon:~$ unset IFS

(There was another empty word generated in one of our previous examples, where IFS was set to /. The observant reader will have noticed, therefore, that non-whitespace IFS characters are not ignored at the beginning and end of expansions, the way whitespace IFS characters are.)

Finally, we note that pathname expansion happens after word splitting, and can produce some very shocking results.

griffon:~$ getent passwd qmaild
qmaild:*:994:998::/var/qmail:/sbin/nologin
griffon:~$ IFS=:; args $(getent passwd qmaild)
737 args: 'qmaild' '00INDEX.lsof' '03' '037_ftpd.patch' ... 
griffon:~$ unset IFS

The * word, produced by the shell's word splitting, was then expanded as a glob, resulting in several hundred new and exciting words. This can be disastrous if it happens unexpectedly. As with most of the dangerous features of the shell, it is retained because "it's always worked that way". In fact, it can be used for good, if you're very careful:

griffon:/music/Yello$ files='*.mp3 *.ogg'
griffon:/music/Yello$ args $files
2 args: 'Yello - Oh Yeah.mp3' '*.ogg' 

Pathname expansion can be disabled with set -f.

A few other notes:


CategoryShell