Z shell made easy

Command line

Following on from our other articles that make various Linux tasks easy, in this article we'll introduce Zsh (Z shell), a shell that has many of the benefits of Bash and others, plus a lot more on top. After reading this, you'll have a good idea about the power of Zsh and will be able to make an informed decision whether to switch from your distro's default shell. We'll be looking more at the interactive use of Zsh, and less at non‑interactive shell scripts - that is, we'll focus on daily use, and not on scripting and automating tasks.

A handy feature of many shells is globbing, also known as filename generation. An example that you're probably familiar with is when you type *.txt to mean all files that end in the .txt file extension. Your shell expands this to the list of all these files, which it passes on to the command - for example, as in ls *.txt.

In Bash, globbing is rather limited. For example, what if you want to list all .txt files in subdirectories of the current directory? Of course, you can try ls */*.txt, but this only results in .txt files that are in immediate subdirectories. Zsh has a powerful solution to match files recursively: ls **/*.txt, which returns all .txt files in all subdirectories. This is so powerful that it almost always makes the find command redundant. For example, instead of find . -name *.txt | grep foo, Zsh allows the much simpler command grep foo **/*.txt. If you want Zsh to follow symbolic links, then you can use ***/, but watch out for infinite loops.

Other than this, Zsh also knows […], which matches any of the enclosed characters. For example, *.[cho] matches all files with extension c, h or o. With [^…] or [!…] Zsh matches all characters that aren't enclosed.

Using qualifiers

Glob qualifiers are another nice addition to Zsh: it has the ability to select types of files by using some flags in parentheses at the end of the globbing pattern. For example, to list only the directories in the current directory, use:

print *(/)

You can use (.) for regular files only, (/) for directories, (*) for executable files, (@) for symlinks, (=) for sockets, (p) for named pipes, (%) for device files, (%b) for block files and (%c) for character files.

In the same way, you can test for file permissions: (r), (w) and (x) for files that are readable, writeable and executable by the owner, (R), (W) and (X) for those with world permissions, and (A), (I) and (E) for group permissions. For example, to find all executable files inside the current directory tree, try:

ls **/*(.x)

If you'd prefer to use the symbolic arguments that the chmod command knows, then you can do this too:

print *(f:gu+rw,o-rwx:)

Another useful glob qualifier tests for the user or group that owns a file. To test for your own user or group, just use (U) or (G), respectively. For other users, you have to add the user or group ID to (u) or (g). So with (u0) you search for all files owned by root and with (u1001) for all files owned by the user with ID 1001. You can also use names if you enclose them with colons: (u:koan:) selects files owned by the user koan.

Zsh can also pick out files by modification or access time with (m) or (a), respectively. You can search for exact times, or periods before (-) or after (+) your chosen point. This is given in days by default, but can also be measured in months, (M), weeks, (w), hours, (h), minutes, (m) or seconds, (s).

For example, here's how you search for all the files you accessed within the last week:

print **/*(.aw-1)

Or you can search for files you modified during the last hour:

print **/*(.m0)

File size glob qualifiers work in the same way: (L) refers to the file size, which is measured in bytes by default, but can also be measured in kilobytes (k), megabytes (m), or 512‑byte blocks (p). This makes it easy to look for all files in the current directory that are larger than a megabyte:

print *(Lm+1)

These glob qualifiers can also be combined at will. The results may be lengthy, but if you know the basic qualifiers you can make sense of them. For instance, *(u0WLk+10m0) are files owned by root, world-writable, more than 10k in size and modified during the last hour. You can use ^ before a qualifier to negate it or , for or. See the man page for even more glob qualifiers, including how to sort the output in different ways.

There are other ways that Zsh can make your life easier.Every shell allows aliases, so that if you often find yourself using the same commands, you can define an alternative name that's easier to type. But Zsh can do a lot more.

Defining an alias is simple. For example, if you find yourself often typing ls -lh, you can define a shorter alias, lh, like this:

alias lh="ls -lh"

Aliases are a convenience for the user, so you have to define them in your zshrc file for interactive shells. When you type lh after the alias has been defined, the shell translates it to ls -lh. This also works if you add some arguments or options to lh, as in lh -a foodir/.

Startup files

Every shell has some startup files that it consults for its configuration. Zsh has system-wide startup items in /etc/ (or, in distributions such as Ubuntu, in /etc/zsh/) and user-specific startup files (in the home directory). When Zsh starts up, it reads the following things in this order:

  • /etc/zshenv and ~/.zshenv
  • If the shell is a login shell: /etc/zprofile and ~/.zprofile
  • If it’s an interactive shell: /etc/zshrc and ~/.zshrc
  • If the shell is a login shell: /etc/zlogin and ~/.zlogin

And when a user logs out from a login shell, Zsh reads /etc/zlogout and ~/.zlogout.

To work out which commands you have to write in which startup files, it's important to know the different types of shells. A login shell is one that's spawned when you log in - for example, via SSH or on a virtual terminal.

An interactive shell displays a prompt to the user where you can type commands - for instance, when you open a terminal window in Ubuntu. However, if you run ssh host somecommand, then this is a login shell, but is in fact a non-interactive one.

Global aliases

What if you want an alias for commands that aren't the first word on the command line? Zsh has also thought about this: it calls these global aliases. You can define them using the -g option to the alias command, as in this example:

alias -g L="|less"

Now you can easily page through the output of another command by adding L to it - for example with dmesg L.

Other handy global aliases are:

alias -g ...='../..'
alias -g ....='../../..'
alias -g .....='../../../..'

Another kind of variant are suffix aliases. Some instructive examples include the following:

alias -s tex=vim
alias -s pdf=xpdf
alias -s html=w3m

This says that if we enter a file as if it were a command, it will be opened by Vim if it has the .tex extension, by xpdf if it has the .pdf extension and by w3m if it has the .html extension.

You can also create aliases for directories, although it doesn't work using the alias command. Aliases that are already defined by the shell are ~, which expands to the user's home directory, and ~user for the home directory of each user. With hash, you can define your own names for arbitrary directories. For example:

hash -d down=~/Desktop/Downloads

Now you can go into this directory from everywhere by using the command cd ~down.

While it isn't really an alias, another option that makes working with directories simpler is AUTO_CD. If you've set it with setopt AUTO_CD in your zshrc, and you type something with no arguments that isn't a command, then Zsh checks whether it's a directory. If it is, then it changes to the directory. So, if you type Documents, it's as if you've typed cd Documents. Changing to the parent directory now becomes even simpler: just use .. instead of cd ...

Most shells automatically complete filenames if you press the Tab key. When using Bash, you can extend this behaviour to complete hostnames and so on by installing the bash-completion package. Zsh has a powerful and fully programmable completion system, but it isn't enabled by default. To enable it, set the following in your zshrc:

autoload -U compinit
compinit

After enabling the Zsh completion system, try typing a few typical Linux commands, but instead of typing the arguments, press Tab to see how smart Zsh is and what it can fill in. For example, Zsh is clever enough only to complete directories after you type cd. Another example is that if you type tar -xvzf and press Tab, it only shows filenames in the current directory ending in .tar.gz. If you've typed tar -xvf, it only shows .tar files. You can even extract particular files from a tar.gz file without having to enter the complete path name.

When you're used to this completion system, you'll find that you stop typing cd, ls and so on to find out where you are and what files there are. You just start typing your commands and let the completion system find the files for you. This doesn't only work for files but also for shell variables, usernames after the -user option and hostnames, and usernames for SSH. It even completes files on a remote user account when using SCP to a server with public keys!

Most users won't have to change the default completion system in Zsh because it's already fairly sophisticated. However, you can always extend the system with your own completion rules or change the default ones. Let's give an example for SSH. By default, it completes all local users and all hostnames in .ssh/known_hosts, but if you just need to log into a couple of machines, it's better to set your own completion rule. For example:

zstyle ':completion:*' users-hosts koan@vervloesem.eu kvervloe@discovery.example.org

The nice thing about this rule is that if you type koan and then press Tab, Zsh already knows that the hostname should be vervloesem.eu because the other hostname has another matching username.

You can also add different colours to the completion list - as displayed in the screenshot below. To be more specific, we'll use the same colours that GNU ls shows with the --color option:

zmodload -i zsh/complist
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}

This presupposes that LS_COLORS is set as an environment variable. If this isn't the case in your distribution, you need to run the dircolors command and copy the output to your zshrc before the above lines.

Spice up your completion lists with a variety of bright colours.

Spice up your completion lists with a variety of bright colours.

Shell prompts

The shell prompt is probably the first thing you'll want to customise. Knowing that a power user sees the shell prompt thousands of times a day, you should take your time thinking about what you want it to show. Luckily, this is an area in which Zsh shines. The prompt is controlled by the PROMPT variable. For example, the default Zsh prompt is "%m%#", which shows the short hostname followed by a % (for a normal user) or a # (for root).

A unique feature of Zsh is that it also supports a right-hand prompt, controlled by the RPROMPT variable, which has the same syntax as PROMPT. This comes in handy if you don't like a left-hand prompt that changes in length. For example, to show the current directory just set "%~" as your right-hand prompt. Zsh is also clever enough to deal with long right-hand prompts. For instance, if your current directory is deep inside your directory tree structure, as soon as the command you're typing in comes close to the right-hand prompt, it disappears.

You can also define conditional substrings in prompts. This is handy if you want another thing in the prompt based on the result of an expression. An example of this is if you want to show a O if the last command exited successfully, but an X if not. This is how it goes:

PROMPT="%(?.O.X)"

Zsh also comes with a couple of prompt themes that can easily be installed. First, initialise the functionality like this:

autoload -U promptinit
promptinit

Once that's done, you can use the prompt command to select a theme. With prompt -p you can preview all themes, and with prompt -s themename you set themename as the current theme. If you want to be surprised, use prompt -s random.

Zsh prompt variables

%/ The current working directory

%~The short form of the current working directory

%t The time in 12-hour format

%T The time in 24-hour format

%* The time in 24-hour format with seconds

%n The username

%m The short hostname

%M The long hostname

%# % for a regular user and # for root

%? The status of the last command (0 for success)

Add colours

A monochrome prompt is rather dull and doesn't distinguish your prompt from the surrounding output of your commands. If you give your prompt some colour, it's not only nicer to look at but also more structured. You can use colours in your prompt by referring to the right escape sequences.

Zsh comes with a shell function called colors that, when loaded and run, defines associative arrays $fg and $bg with the correct escape sequences for given colours. For example, ${fg[red]}${bg[yellow]} produces the sequences for red text on a yellow background. One thing to remember is that you always have to surround sequences that don't move the cursor with `%{' at the start and `%}' at the end.

To load these colours, set the following in your zshrc:

autoload colors zsh/terminfo
if [[ "$terminfo[colors]" -ge 8 ]]; then
  colors
fi

Now if we go back to our conditional expression with a O or an X, we can add colours to it: if the last command exited successfully, we show a green O, otherwise a red X:

PROMPT="%(?.%{${fg[green]}%}O.%{${fg[red]}%}X)%{${fg[default]}%}"
Try some prompt themes with the prompt command, and make the output easier to interpret by using Zsh's built-in colour functionality.

Try some prompt themes with the prompt command, and make the output easier to interpret by using Zsh's built-in colour functionality.

Zsh also makes it possible to run particular code automatically on certain occasions. You just have to define some special functions. The two most frequently used are chpwd and precmd. Zsh calls the former each time the current directory changes. The latter is called just before Zsh shows you a new prompt. Both functions are regularly used to show the current directory in the title bar of your terminal emulator. If you use programs other than the shell, which alter the title of your terminal emulator (Vim is one example), you should use precmd - it restores the title after another command has run. So this is how we show the current directory in the title bar (adapted from the manual page):

precmd() {
  [[ -t 1 ]] || return
  case $TERM in
    (sun-cmd) print -Pn "\e]l%~\e\\"
      ;;
    (*xterm*|rxvt|(dt|k|E)term) print -Pn "\e]2;%~\a"
      ;;
  esac
}

There's also a function periodic() that is executed every PERIOD seconds if the latter variable is set.

A handy utility is time, which shows the total CPU time used by a command. The only problem is that you have to remember to put time in front of the command name before you start it. What if you started a command that takes unusually long and you want to know how long it executed?

In Zsh, you can do this by setting the variable REPORTTIME to a number of seconds: if a command runs for more than this many seconds, the shell shows time information after it exits. You can specify the information that's shown in the TIMEFMT variable. For instance:

REPORTTIME=5
TIMEFMT="%U user %S system %P cpu %*Es total"

The Emacs of shells

It should be clear by now that Zsh has a steep learning curve, but it can offer you a lot if you take the time to learn the basics. Luckily, there are a lot of good resources about this shell. It has comprehensive man pages, and you can also turn to the online manual, called A User's Guide to the Z-Shell, by Peter Stephenson (available at http://zsh.sourceforge.net/Guide/zshguide.html), which is a bit dated, but remains excellent and in-depth. Another resource for Zsh lovers is zshwiki.org. So get customising your Zsh config and benefit from your improved shell.

If you've been impressed by the power and flexibility of Zsh and want to get to know it better, change your default shell to /usr/bin/zsh.

If you've been impressed by the power and flexibility of Zsh and want to get to know it better, change your default shell to /usr/bin/zsh.

First published in Linux Format

First published in Linux Format magazine

You should follow us on Identi.ca or Twitter


Your comments

Infinite loops

Nice article, thanks. I Just wanted to point out something:

"If you want Zsh to follow symbolic links, then you can use ***/, but watch out for infinite loops."

AFAIK the Linux kernel prevents symbolic link infinite loops with a maximum of 8 recursions.

I haven't tested it though.

Excellent

I've been meaning to give Zsh a try for quite a while now, maybe this will motivate me.

Worth the switch

Prompted by zsh discussion a few Tuxradar podcasts ago, I switched from bash and haven't looked back. Thanks for the prcmd script, I hadn't implemented that.

I'm with "mifrosu"

I started using it after mention on the podcast. It's pretty *(*£* sweet. Tab actually works.

It should be the default for any linux operating system.

What can't Zsh do?

i've switched

ive switched from bash on my box. Thanks!!

grep invocation?

in this sententce :
======QUOTE========
For example, instead of find . -name *.txt | grep foo, Zsh allows the much simpler command grep foo **/*.txt.
======QUOTE========
you call grep on the list of files returned by zsh , not grepping the filenames which contains foo.

great article , I will give a try to zsh ! :D

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar