Migrating to Zsh

A Better Shell


The modern Z-Shell with its innumerable useful features gives command line fans an attractive alternative to Bash.

By Simon Kölsch

Today, a shell must do a lot more than just interpret commands. A modern shell can complete any number of commands and parameters; it will have a scripting language of its own, and it will include functions that make the user's life much easier. Most distributions use Bash as the default shell. But if you make heavy use of the command line, you may be interested in Z-Shell, a shell with several interesting options you won't find in Bash.

Z-Shell (or Zsh for short) was developed in 1989 by Paul Falstad, whose idea was to write a new shell that combined the benefits and improvements that Bash, Csh, and Ksh offered, while at the same time adding new features. The members of the zsh-workers@sunsite.dk mailing list now continue this good work, which is coordinated by Peter Stephenson.

Installation

Many distributions include Zsh by default, and with others, you can easily use the package manager for the install. RPM packages are available from RPMSeek [1], for example; you can give the rpm -i packagename.rpm command to install.

If you prefer, you can build Z-Shell from the current source code. The source code is available from any of the numerous Zsh project mirrors. There are no specific dependencies to watch out for. Use standard procedures to compile and (working as root) install Zsh:

./configure
make
su -c "make install"

Migrating to Zsh

If you want to try out the examples in this article, you can launch Zsh by entering the exec zsh command. But to use Zsh as your login shell, you must first check to see if your system has an entry with the full path to Zsh in your /etc/shells file. If this entry is missing, first type which zsh to discover the path to Zsh and then (working as root) add the path to /etc/shells (see Listing 1).

Listing 1: Checking /etc/shells
01 # grep zsh /etc/shells
02 # where zsh
03 /usr/bin/zsh
04 # echo "/usr/bin/zsh" >> /etc/shells

To replace your current login shell with Zsh, type chsh -c /path/to/zsh username. You may need to run chsh as root.

When you change the shell, it makes sense to stay logged in at another console as the user changing the shell in order to check if the changes work. A typo in the path name would prevent the user from logging in without entering the chsh command once more or editing the /etc/passwd file.

If you prefer, you can can use the -l or --login parameter to launch Zsh explicitly as the login shell.

Configuration Files

System administrators have five configuration files for configuring Zsh: /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin, and /etc/zlogout. (Debian puts these files in /etc/zsh.) The files can also be stored in each user's home directory. You can use the $ZDOTDIR environmental variable to specify a different location; $HOME - ZDOTDIR=$HOME/.zsh, for example.

The convention is to start user-specific configuration file names with a dot, such as .zshenv, .zprofile, and so on. To improve readability, we will be leaving out the pathname and the initial dot in the following configuration files.

When it launches, Zsh first parses the central configuration files (in /etc or /etc/zsh), before going on to parse user-specific files. This means that any user can modify the central configuration, as designed by the system administrator, with their own configuration files.

Z-Shell parses zshenv first. This is where critical environmental variables such as paths (PATH, CDPATH, MANPATH) or settings such as DISPLAY, EDITOR, PAGER, and PRINTER are located.

If Zsh is the login shell, it goes on to parse zprofile and zlogin after zshenv. Every Z-Shell (no matter whether it is the login shell) then parses zshrc. Before quitting, a Zsh login shell will also parse zlogout.

There is no need to customize the configuration files: like most shells, Zsh is quite happy to run without you modifying the configuration. This said, the configuration files give you plenty of leeway for tailoring the shell to your own requirements. Table 1 tells you which files are responsible for which tasks.

History

Zsh can store a list of the commands you enter in a so-called history. To enable the history, you need to set three environmental variables: HISTSIZE, HISTFILE, and SAVEHIST. HISTSIZE specifies the maximum number of entries that Zsh will store in the history. HISTFILE specifies the file where Zsh stores the history before quitting. And finally, SAVEHIST defines the number of lines that Zsh can write to the $HISTFILE. A typical configuration looks like this:

HISTSIZE=500
 HISTFILE=~/.zsh_history
 SAVEHIST=100

This tells Z-Shell to "remember" the last 500 command lines, but only to store the last 100 in the specified file. The next time you launch Zsh, you can recall the stored command lines via the history function.

Additionally, pressing [CTRL]+[R] allows you to search the history for commands. Pressing this shortcut multiple times scrolls through the lines that contain the pattern you are looking for. ^ represents the start of line and can precede the search pattern. If Zsh finds the required command, you can then press [Enter] to run the command once more. [CTRL]+[4] quits the search.

The Prompt

A clear cut prompt facilitates effective use of the shell and has a major impact on the shell's appearance. The Zsh prompt uses a different notation from Bash, and this will prevent you from simply copying an existing bash configuration file.

The PROMPT environmental variable defines the appearance of the prompt. It contains wildcards for system information and paths. The shell interprets the contents of this variable and displays the prompt to match. As PROMPT is an environmental variable that is set whenever the shell launches, the entry should be placed in zshenv. You will find an overview of the wildcards and how they are expanded to form the prompt in the zshmisc manual below "Prompt Expansion":

man zshmisc | less +/^PROMPT

In the following section we will develop a practical prompt that leverages two of the Z-Shell's useful properties. We will be restricting the current directory display to the last few superordinate directories to prevent the prompt from becoming unwieldy. Additionally, Zsh supports conditions, and we can draw on them to react to specific states with specific events. We will be using this ability to evaluate the return value of the last command to see at a glance if an error has occurred. This is particularly useful if you need to run a command that sends a lot of output to stdout - make, for example.

A Custom Prompt

Let's look at the default prompt to get the ball rolling; it follows the username@host pattern and looks like this:

PROMPT=%n@%m

The %m notation refers to the hostname, and %n stands for the username. Let's add a separator between the prompt and the command line entry: %# will give you a pound sign whenever root uses the shell and a percent sign for normal users:

PROMPT='%n@%m%#'

We can now add %~ to output the current directory, and a colon to separate the directory from the hostname. To give the prompt a neat appearance, let's put the whole thing in square brackets and add a space before the final character:

PROMPT=$'[%n@%m:%~] %#'

If the current directory is more than four levels removed from the root directory, the prompt we have just designed starts to look ragged: what's more, it creeps farther to the right the deeper you dig into the directory tree. To prevent this from happening, you might like to display just the name of the current directory from the fourth level onward. This is referred to as truncating, and Zsh uses conditions to handle it. The general syntax for this is %(condition,action if true,action if untrue).

There is no need to use a comma to separate conditions and actions. Zsh will interpret the first character to follow the condition as the separator. You just need to make sure you use the same separator for the remainder of the expression.

The condition we need here is 4c. c refers to the number elements in the current path (/ does not count as an element); 4 checks if the path contains four or more elements. As an action, you can either print the whole path, by specifying %~, or just the first element in the path, that is, the current directory, by specifying %1~. It also makes sense to indicate that we are using truncation by adding ./ to the truncated path:

PROMPT=$'[%n@%m:%(4c,./%1~,%~)]%# '

A simple condition gives you the return value for the last command:

PROMPT=$'[%n@%m:%(4c,./%1~,%~)]%(?,:%),:%() %# '

The condition ? is fulfilled if the exit status of the last command was 0, that is, if no errors have occurred. In this case, Zsh will append a smiley to the prompt. The emoticon will frown if the return value was not zero.

The prompt also supports the use of color for improved readability. If you would like to color brackets and the @ sign green, and the smiley that indicates the return value red or green, you can use escape sequences to do so.

In Zsh, you specify an escape sequence with ${}. For example, the following sequence will display the @ sign in green:

%{e\[0;32m%}@%{e\[0m%}

The second, closing escape sequence e\[0m% ensures that any following characters are displayed in normal type. You could also use bold type to enhance the appearance of your prompt. In this case, %B will toggle the Zsh text to bold, and %b will revert to the normal display:

PROMPT=$'%{\e[0;32m%}%B[%b%{\e[0m%}%n%{\e[0;32m%}@%{\e[0m%}%m%{\e[0;32m%}%B:%b%{\e[0m%}%(4c,./%1~,%~)%{\e[0;32m%}%B]%b%{\e[0m%}%(?,%{\e[0;32m%}:%)%{\e[0m%},%{\e[0;31m%}:(%{\e[0m%}) %# '

If you add all the escape sequences we have discussed thus far to your prompt, the results should look like Figure 1.

Figure 1: Using escape sequences to color the Zsh prompt. A green smiley indicates error free command execution; a red emoticon tells you that something has gone wrong.

Interesting Options

Zsh has a number of options you can enable using the setopt command. To make an option permanent, add the setopt command and the required parameters to the zshrc file. The parameter name can contain upper and lower case letters, and underscores are permissible in most cases. For example, Zsh would understand autocd, AUTOCD, or even Auto_cd.

The zshoptions manpage has a list of options. To disable an option, place a "no" string in front of the name: setopt no_Auto_cd or setopt NOAUTOCD.

Typing setopt without setting any parameters tells you which options have been set. setopt also supports tab completion. If you need a complete option list (see Figure 2), simply type setopt and press the tab key twice. The most important of the 150 or so options are as follows:

Figure 2: To display the full set of Z-Shell options, enter "setopt" and press the tabulator key twice.

Globbing

One of Zsh's most interesting features is its powerful globbing system. The term globbing refers to the way the glob tool works, that is, the way files and folders are found by reference to search patterns such as regular expressions.

The setopt EXTENDEDGLOB command enables extended globbing: you can then use arguments such as <x-y> to address numeric ranges. Also, parameters such as (foo|bar) support argument groups, and   searches the filesystem tree recursively:

% chmod 644 ~/public_html/**/*.html(.)

Qualifiers allow you to modify and focus your search results. To do so, add a qualifier to the search pattern as follows: pattern(qualifier). For example, if you would like to find programs in /usr/bin with the SETUID bit set, you can add an s qualifier:

ls /usr/bin/*(s)

The zshexpn manpage has a complete list of qualifiers. The most common qualifiers follow:

Figure 3 shows a few applications for Zsh globbing. You can negate the result by adding a preceding ^. For example, ls *(^U) will give you files that belong to other users, but not your own files.

Figure 3: Globbing and qualifiers make file searches with Z-Shell child's play.

If you set the EXTENDEDGLOB parameter, the noglob modifier can prove very useful in some cases. It disables globbing for the following command. For example, the following command does not download the page at the specified URL but causes an error message:

% wget http://www.foobar.de/index.php?action=test
zsh: no matches found: http://www.foobar.de/index.php?action=test

When globbing is enabled, Zsh interprets the question mark as the start of a search pattern, and because the expected search pattern is missing, Zsh then issues an error message.

If you give the noglob command to disable globbing, this also disables filename expansion, and the parameter is not expanded. Instead, it is passed "as is" to wget, which then downloads the requested website:

% noglob wget http://www.foobar.de/index.php?test
--11:41:25-- http://www.foobar.de/index.php?action=test
    => `index.php?action=test'
(...)

Sources

If this short excursion into the world of Zsh has whet your appetite, and you are now looking to investigate Z-Shell more thoroughly, you might like to check out the following sources of information. On the official Z-Shell site, you will find a short introduction to Zsh [2] and the official Zsh FAQ [3].

For an overview of Zsh's features and a comparison with the other shells, you might like to check out Christian Schneider's page at [4]. There is also the smaller Zsh Lovers project, which collects examples of the functions described in the manpages and publishes them in the form of the Zsh Lovers manpage [5].

Finally, let's not forget the excellent printed reference, which will definitely take the pain out of migrating from Bash: From Bash to Zsh (ISBN 1-59050-376-6) by Oliver Kiddle, Jerry Peek, and Peter Stephenson, is published by Apress and costs US$ 35.00. And if all that searching in online sources or literature takes too long, you can always exchange experiences with other Zsh users via the IRC channels #grml and #zsh on Freenode (irc.freenode.org).

INFO
[1] RPMSeek: http://www.rpmseek.com
[2] Zsh intro: http://zsh.sunsite.dk/Intro/
[3] Zsh FAQ: http://zsh.sunsite.dk/FAQ/
[4] Christan Schneider's Zsh page: http://www.strcat.de/zsh/
[5] Zsh Lovers: http://grml.org/zsh/#zshlovers