ZSH Gem #3: No automatic word split

Posted by | Comments (2) | Trackbacks (0)

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:

Trackbacks

No Trackbacks for this entry.

Comments

There have been 2 comments submitted yet. Add one as well!
foo
foo wrote on : (permalink)
Hi, Nice blog. Please continue it! The following will also work: var2=(${(ps: :)var1})
Charles
Charles wrote on : (permalink)
Thank you very much. This helps me a lot to get rid of something like this: IFS=/ read -A pwdarr

Write a comment:

E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

By submitting a comment, you agree to our privacy policy.

Design and Code Copyright © 2010-2024 Janek Bevendorff Content on this site is published under the terms of the GNU Free Documentation License (GFDL). You may redistribute content only in compliance with these terms.