ZSH Gem #3: No automatic word split
Have you ever stumbled upon this: you write a shell script which works with variables containing spaces and suddenly the whole script breaks? This is because most shells automatically split strings on spaces. This is some ancient behavior and in most cases it is just a pain in the ass.
Take this example: you write this (pointless) script where a user has to specify a directory. The script then lists all files in this directory:
#!/bin/bash
read -p "Please enter the directory you want to list: " dirname
if [ -d $dirname ]; then
ls -la $dirname
else
echo "$dirname does not exist or is not a directory."
fi
Now imagine the user would input something like foo bar baz. Not only would ls
try to list the contents of three directories named foo, bar and baz instead of the directory foo bar baz
, also the condition before would fail:
./testscript: line 4: [: bla: binary operator expected
This behavior is enabled by default in Bash. All strings are splitted at certain characters defined in the variable $IFS
. So to avoid this you'd either have to set all (and when I say all I mean each and every) occurrences of $dirname
in quotes ("$dirname"
) or you'd have to empty the variable $IFS
(IFS=""
). But obviously, both variants are not very elegant and I consider them dirty workarounds, not options to turn off word split. The first variant is a lot more to type and you'll run very quickly into some double quoting issues where you need to escape quote characters and the second one… well, I better not comment.
In ZSH, however, word splitting is disabled by default (which is great) and there is not just a variable containing the splitting characters but also an option to toggle this behavior. To turn word split on, execute the following line:
setopt sh_word_split
and to disable it again:
unsetopt sh_word_split
No need to back up and restore the contents of some mysterious variable (or alternatively unset it to restore the default value). As mentioned above, $IFS
also exists in ZSH but you don't need to touch it in order to configure the word split behavior. Here you have a clear and clean option and not a workaround.
Now you might say that word splitting is very useful when it comes to loops. For instance:
values="foo bar baz"
for i in $values; do
echo "Current value: $i"
done
But to be honest, why the hell would you use strings for that? Normal people use arrays.
values=("foo" "bar" "baz")
for i in $values; do
echo "Current value: $i"
done
Bang! No need for any obscure word splitting magic. And provided there is a case where you need to split a string at specific characters, just do it manually using parameter expansion flags when needed:
var1="foo bar baz"
var2=(${(ps: :)${var1}})
echo $var2[1]
Voilà! This splits the string on each space character. But of course, it does not always have to be a space character. You can use any character you like. E.g. to split the string on dashes, use var2=(${(ps:-:)${var1}})
. It's as easy as that!
I hope, you learned a thing or two and keep your hands off word splitting from now on. It can be a handy thing, but in general is does more harm than good.
Read more about word split:
- zsh.sf.net FAQ: Why does $var where var="foo bar" not do what I expect?
- zsh.sf.net: Parameter Expansion Flags (e.g. for splitting strings into arrays)