We can make life easier if we simply learn some new tricks, so let’s try a little experiment: I’m going to switch shells, converting everything in my tcsh configuration into zsh equivalents, and hopefully learn some new tricks. This week we’ll talk about some neat things you can do with both zsh and bash, and next week will have details about the big switcheroo.
There are many reasons to choose one Unix shell over another. You don’t even need to switch shells sometimes; an upgrade often provides amazing new features. Unfortunately tcsh isn’t going to become nearly as powerful as zsh, so a conversion is in order in this case. We aren’t trying to start a religious war; zsh is on a completely different plane than tcsh.
The bash shell is so widely used many people are wondering, “why zsh?” One of the main reasons people enjoy zsh is because it supports “completions.” Not just filename completion, it’s much more powerful than that. In fact, bash now supports completions as well. Zsh feels more configurable, support many other new and wonderful features, and has better defaults, so we chose zsh.
Scripting in zsh, however, is strange. Don’t worry; just continue writing in bourne shell syntax if you’d like to avoid learning the idiosyncrasies of zsh’s scripting engine. One feature that’s notable, though, is that zsh can perform arithmetic without calling expr. It’s derived from the bourne shell, like bash, and most of zsh’s power comes from its interactive uses.
Take, for example, the need to rename a bunch of files in a directory. Let’s say we have a bunch of files named *.txt and we’d like to rename them to *.text, for some strange reason. We could loop through all the files and think really hard about how to do it in bourne/bash, or we could use a zsh built-in function called zmv:
zmv -W '*.txt' '*.text'
The zmv function is very complicated and powerful, so be sure to read up before using it. It’s even possible to operate on all directories, recursively. This is done with zsh recursive globbing. An example explains it best:
wc -l **/*.sh
This will expand all *.sh files in the current directory, and subdirectories, before executing ‘wc.’ The equivalent in any other shell requires the find command: wc -l `find . -name *.sh
The main magic happening here is that ‘/’ is being expanded to mean directories. It’s equivalent to saying (/), which is a globbing modifier. This is useful for many things, and it can be used anywhere. An example is listing only directories, or pretty much any attributes that you can specify with the find command. List everything that starts with D, but only if it’s a directory and group writable: ls -ld D*(/I)
We can use the zmv command again with this new knowledge, to do the same thing, but recursively in all directories: zmv -W ‘**/*.txt’ ‘**/*.text’
The zmv function was probably my number one reason for choosing zsh. Completions are very nice too, but the benefits of zmv are more obvious. Understanding how completions simplify your life requires that they be used for a while. You’ll begin to get used to tab-completing interesting things, and wonder then how you ever lived without it.
These “completions” we keep referring to are the same old tab completions you’re used to in other shells, but zsh allows you to define function to complete certain aspects of frequently used commands.
Configuring completion isn’t for the faint of heart. There are examples online, the most common being completing hostnames for the ssh and scp commands. The general idea is to specify which commands you want to run when completing parts of a certain command. For example, we may wish to parse /etc/hosts for completing host names when scp or ssh is run. Note that there’s no reason to pipe output through sed and awk—zsh can perform text parsing internally for you. Thankfully, zsh can save these lists into files, so tons of commands don’t need to be run each time we attempt a completion.
Zsh also allows you to compile its configuration. These configuration files can get quite large and complex, so zsh provides zcompile to enable quick parsing, i.e. quicker logins.
You’ll find that zsh has tons of other built-in goodies too. One great feature is filename expansion. Its many functions are quite confusing, but the “=” sign is worth noting. You can say: ‘ls -l =ssh’ instead of the old ‘ls -l `which ssh`’. The expansion of ‘=’ is such that the string immediately following the equals sign is taken as the name of a command, and it’s replaced with the full path to the command. This too is a great timesaver. Also, if auto_cd is set as an option, you can just type a path to change to a new directory without the ‘cd’ command. And switching back to the previous location is a snap with ‘~-‘ which expands to ‘cd $OLDPWD’.
Directory stacks are, of course, supported as well. In fact, zsh can automatically (and silently) create a stack of directories you’ve been to. It even removes duplicates for you, if so configured.
The spiffy zsh-isms are nearly endless. It’s extremely easy to waste time playing with all the options, and the best part is that you don’t generally have to remember what you did. Most shell modifications in tcsh, for example, take the form of fancy aliases, which you must remember in order to use. Most modifications in zsh are going to modify the way the shell operates, so they’re both powerful and useful.
Like all notable software, we’ve only scratched the surface of its capabilities. Converting a tcsh configuration into a brand new zsh setup will be quite the learning experience, and I’ll also provide a detailed description of the most basic, but necessary, options. Be sure to return next week for the follow-up.