Shell Game: Trading in csh for zsh (Part 2)

Old shell habits die hard, especially for proficient scripters. Our reviewer left the comfort of csh for a foray into zsh and decided it's not just a nice place to visit.

Last week we took the plunge and started learning about zsh. The remaining question was, "what does it take to convert an avid csh/tcsh user to zsh?" The answer was surprisingly, "not much."

Zsh implements many options that will appeal to csh holdbacks. Many are limiting—they simply disable some functionality—others make the transitions smoother for people who wish to keep the same functionality. While attempting to avoid using the blatant "do what csh does" options, it became clear that the abundance of configuration options was going to make this fairly trivial.

First, a word on configuration files. It's important to know where and what files are sourced at login time. The .zshenv file is sourced at every invocation of zsh, unless -f is set. So it's a good idea to set a minimal PATH here; it will be used for non-interactive sessions. Common errors with rsync, for example, occur when the rsync command cannot be found on the remote server. This is also very useful for people who like to run remote commands over ssh without actually logging in interactively: 'ssh server1 <command>'

The .zshrc file is used for all interactive logins, and should be used to set up aliases, functions, and key bindings. Just like bash, .zprofile is sourced before .zshrc, and can be used to set terminal types or run commands.

Logic Is

Switching from csh to zsh means rewriting everything in bourne shell-like syntax. Reworking 'if' logic, changing aliases to include an equals sign, and paying attention to the alias limitations are the main changes required.

In csh we might have had something like:

if ( ! -d /etc/opt/OV ) goto End
"stuff"
End

This needs to be converted to bourne-style syntax. Also note that there are no goto capabilities in zsh. Converting is easy enough:

if [ -d /etc/opt/OV ] ; then
"stuff" ; fi

A really big problem for some is that aliases don't take arguments in zsh. In csh you could set up some spiffy little aliases that operated on the arguments, but not in zsh. An alternative was never found for:

alias prepend 'setenv !:1 "!:2":${!:1}'

This was a beautiful way to prepend any environment variable, for example one's PATH, by saying, "prepend PATH /new/directory." Thankfully, zsh can do this very easily:

path=(/new/place $path)

This is a simple array, and zsh just does the right thing if it's an environment variable. In fact, if you 'echo $path' you'll see the whole array, in space delimited form instead of $PATH's colon separated format.

Another great feature of bourne derivatives is the :- logic. It sure makes setting up global (for all users) environment settings easier. Debian, for example, uses this in the default zshrc file: READNULLCMD=${PAGER:-/usr/bin/pager}. It says: if $PAGER is unset, use /usr/bin/pager.

READNULLCMD is the command that zsh will assume if you use the null command while reading. NULLCMD is for redirecting NULL into a file (writing). For example, I set READNULLCMD to 'cat' so that I never actually need to type 'cat' again. To cat a file, just run: </etc/passwd. If NULLCMD is still set to ':' then truncating files still works: >/etc/passwd will make your passwd file 0 length, so don't actually do that.

Zsh Options

Not missing csh a bit at this point, I kept stumbling across tons of interesting options for zsh. Options are set and unset with 'setopt' and 'unsetopt,' respectively.
One of the neatest options is called "sunkeyboardhack." If set, zsh will ignore any trailing backtick, if it's unmatched. My Sun Type 7 keyboard bas a singlequote near the enter key, so this option isn't so useful any longer. Apparently much older keyboards had a small enter key and the backtick nearby.

Some other very useful options are:setopt allexport - assumes all variable assignments are exported to the environment. Greatly reduced the redundancy of having "export" on many lines in .zshrc.setopt autocd - assume the 'cd' command if just a path is specified on the command line.setopt pushdignoredups - ignores directory stack duplicates.setopt histignoredups - keeps duplicates out of the history.setopt interactivecomment - allows comments in the interactive shell. Useful for pasting.setopt promptsubst - parameter, command, and arithmetic substitution are performed in prompts. setopt extendedglob - enable extended globbing.

The list goes on and on. The zshoptions man page explains what each ones does. Take a look at the zshall manual as well. In addition to providing all pages in one meta-manpage, ringing in at 20K lines, it starts with an explanation of the various zsh manuals.

Neat Tricks
There are a few neat tricks in zsh that have change the way I do things. Aside from extended globbing, which mostly replaces the need for the find command, there's a few other nifty tricks to learn.

In zsh you can edit environment variables directly, without having to copy and paste or reference existing variables in your changes. The secret command is 'vared' which fires up a small editor. It's isn't a real editor, instead it's zsh's built-in called ZLE. If you'd like it to behave like vi, run 'bindkey -v.' The default is emacs-style editing. You can make your edits and hit enter, and the variables are changed. Notice that you can tab-complete all environment variables too!

ZLE can also be used on your history. This is especially useful when you're running multi-line commands that look like full-blown shell scripts. They are no longer collapsed onto one line (as bash does) with semicolon separators. You can hit ^P to bring up the previous command, or just use the up arrow, and it will be displayed exactly how it was entered.

Another seemingly trivial, yet highly useful feature is: ~- (tilde, hyphen)
Zsh expands ~- to $OLDCWD, and if autocd is set, you can just type ~- to cd back to where you came from. A quick and handy way to avoid typing 'pushd' every time you go somewhere temporarily.

And finally: WORDCHARS. If you set this variable to include the items zsh should treat as part of a word, and, for example, leave out the forward slash, you can ^W back over parts of a directory path. The default behavior deletes the entire path, and that's almost never what you want.

In the end, I didn't miss csh at all. Recursive globbing alone would have been enough, but zsh includes so many other useful features that a switch was inevitable. Does it really matter if zsh eats 30MB of memory these days? No, not really.