Skip to main content

Curtains up: introducing the Z shell

Improve the efficiency of your shell interaction

Matt Chapman (mchapman@uk.ibm.com), Software Engineer, IBM Hursley
Matt Chapman graduated with a BSc. in Computer Science from the University of Warwick in 1996. He has since worked as a software engineer in the IBM Centre for Java Technology, in Hursley, UK, and has been an avid Linux and Zsh user for over five years. He can be contacted at mchapman@uk.ibm.com.

Summary:  According to Matt Chapman, the Z shell can improve the efficiency of your shell interaction. It's time that this secret was out! In this article, the Z shell is introduced, and some of its advantages over the other shells, particularly Bash, are explored.

Date:  01 Feb 2001
Level:  Introductory
Activity:  845 views
Comments:  

Improving your interactive UNIX shell experience

Most developers and users of Linux come into contact with a UNIX shell sooner or later. This is typically in the form of the Bash shell, or sometimes the C shell, or Tcsh, or the Korn shell(the default on IBM's AIX operating system). They rarely come across the Z shell which can improve the efficiency of your shell interaction, and save some typing! And for those who are reluctant at the prospect of having to learn "a whole new shell", it's worth noting that Zsh is about as close as possible to a superset of all those other shells, so you can get started straightaway.


Running the Z shell

The Z shell ("Zsh")is usually found on the system as /bin/zsh. If it is not already on your Linux installation, it can be obtained from the install disks of most distributions. Alternatively it can be downloaded from http://www.zsh.org. Incidentally, it is not tied to Linux - the source can be successfully compiled on most UNIX platforms, including AIX, and numerous binaries are available.

All of the shell commands and settings covered in this article can be put in a ~/.zshrc file so that they are used every time you start an interactive shell. You can also have a ~/.zshenv file, which is used for both interactive and non-interactive shells (for example shell scripts). See the Zsh documentation for more details of the Zsh startup process.


Command line editing

By default you can use the cursor keys, and backspace, to move around and edit the input line, just like with the Bash shell. You can also enable Emacs bindings with the command "bindkey -e". Examples of these key sequences include Control-A to jump to the beginning of the line, and Control-K to delete to the end of the line. Alternatively you can do "bindkey -v" for Vi bindings.

Zsh has a multi-line editing mode, which is great for short shell scripts, as in this example:

zsh% for x in 1 2 3
for> do
		[we're in the middle of a "for" command so
for> echo $x 
		Zsh changes the prompt to remind us]
for> done
		[after entering this, Zsh knows we're done
1                so it can run the command]
2
3
zsh% <Cursor-Up>
                [or Control-P with Emacs bindings]
zsh% for x in 1 2 3 
                [the whole four line script appears, and
do		you can then move all through it, editing
echo $x         as required]
done

I will not attempt to cover shell scripts in detail here, but it's worth mentioning that for single line "for" loops, you can actually miss out the "do" and "done" parts.

The commands you enter are stored in a history, and there are numerous ways of accessing this, such as with Cursor-Up in the above example, which allows you to scroll through the history (with Cursor-Down to go the other way of course). Other useful tricks are "!!" to execute the previous command, and "^old^new" which executes the previous command after replacing all occurrences of "old" with "new". This is particularly useful if you made a mistake, or want to just change the command slightly - it can be quicker than recalling the command and then editing it directly.

The Zsh even has a form of spell checker, which attempts to correct various mistakes. This can be dangerous if it "corrects" your command to something you didn't actually mean, so you are always prompted after it detects the correction. Also, this functionality is off by default, you have to enable it with "setopt CORRECT". Here is an example:

zsh% setopt CORRECT 
		[enable option]
zsh% chomd u+x file.sh 
                [we've mistyped "chmod" here]
zsh: correct 'chomd' to 'chmod' [nyae]? y 
		[Zsh detects this and asks if we want
		to correct it]
zsh%            [if yes, then the corrected command
		is executed]


The magic TAB key

The most efficient shell user is a lazy one! You should aim to achieve what you want with as little typing as possible. You'll find throughout this article that this is something Zsh is very good at doing.

Filename completion is not a new concept; many shells provide basic forms of completion, such the Bash shell which uses the TAB key, as in the following example:

bash$ ls
another_file
this_is_a_file_with_a_long_name
bash$ more th<TAB>
		[press the TAB key]
bash$ more this_is_a_file_with_a_long_name
		[the shell magically fills in the rest!]

Pressing the TAB key causes the shell to complete the filename for you. Think of all that typing you've just saved! (Or for those of you that reach for the mouse and cut and paste the filename from an earlier directory listing - think of all the time you save moving your hand to the mouse then refocusing it back on the keyboard.)

Of course, it's not magic at all, just a trivial deduction - you're typing a filename beginning with "th", and there is only one file in the current directory beginning with "th", therefore that's probably the one you're interested in. So let your shell do it for you!

The above example was with the Bash shell, but of course the Zsh can do this too! And a lot more besides. Let's try another example:

bash$ ls
another_file
this_is_a_file_with_a_long_name
this_is_another_file
bash$ more th<TAB>
bash$ more this_is_a<TAB>
		[beeps, try pressing TAB again ]
bash$ more this_is_a
this_is_a_file_with_a_long_name
		[the shell gives you a list of
this_is_another_file
		possible completions]
bash$ more this_is_an<TAB>
		[type more of the required filename]
bash$ more this_is_another_file
		[the shell can then complete it]

As you can see from this,it becomes a bit more complicated when there is more than one match. The Bash shell completes as much of the filename as it can, and then stops. You then have to type more of the filename so that it is then unique before it will complete it. By default the behaviour of Zsh is very similar, but by setting a couple of options, we can make it friendlier. I suggest putting "setopt AUTO_LIST AUTO_MENU" in your ~/.zshrc file, or enter it at the command line. We can now try the following example with the Zsh:

zsh% more th<TAB>
zsh% more this_is_a
		[the shell completes as much as it can
this_is_a_file_with_a_long_name 
		AND displays all the matching names]
this_is_another_file
zsh% more this_is_a<TAB>
                [press TAB again]
zsh% more this_is_a_file_with_a_long_name
		[completes to the first match]
zsh% more this_is_a_file_with_a_long_name<TAB>
		[press TAB to rotate through all matches]
zsh% more this_is_another_file

The shell now displays all of the matches the first time you press TAB, and you can select between them by repeatedly pressing the TAB key. You can type more letters of the filename, like in the Bash example, but where the number of matches is small, it is easier to rotate through them, than to work out which character or characters you need to type to make the filename unique.

Zsh also provides programmable completion. This means it can complete more than just filenames - anything at all in fact, from commands to aliases to environment variables. Whilst all of this is programmable, much of it is enabled as default, so you can benefit from it without having to do any configuration yourself (although we'll try a couple of simple customization too).

Let's start with command completion which, as well as saving typing, is really useful when you can't remember the exact name of a command. Simply type the first part of the command, and then press the TAB key, and you get a list of possible completions, just like with filenames. The shell knows that the first part of input is going to be a command, not a filename, so it completes against commands, both built in commands and external commands (located using the PATH variable), and also shell functions and aliases. It's worth mentioning the "which-command" function which is useful here. After completion, or after typing a command in, run the function, which by default is bound to Esc-? press and release Escape, then press "?"). It simply tells you where the command is located, just as if you had typed "which" followed by the command. Here is an example:

zsh% xf<TAB>
		[complete against commands]
xf86config  xfd         xfindproxy  xfontsel    xfs         xfwp
zsh% xfo<TAB>                    
		[type another letter to make it
		unique, or TAB repeatedly to cycle]
		through completions]
zsh% xfontsel  
                [command has now been completed]
zsh% xfontsel<Esc-?>
		[use Esc-? shortcut]
zsh% which-command xfontsel
		[this shows expansion of shortcut]
/usr/X11R6/bin/xfontsel
zsh% xfontsel   
		[afterwards we're back to the command itself]

Another really useful thing you can complete against is environment variables. Any time you are referring to a variable using the "$" sign, the shell will complete against all variables in scope. So if you do "echo $D" then press TAB, it will complete to "$DISPLAY", if you don't have any other variables which also match (if you do, you'll just get a list of completions as before - completion works the same way whatever you're matching against).

As previously mentioned, you can program the completion completely, to define what is completed and when. This is all controlled by the "compctl" command. (New beta versions of Zsh, from version 3.1.6 onwards, also include a new mechanism but we won't cover that here as they are not in widespread use.) This is a complex area, but much can be achieved with a few short "compctl" lines.

The first, and one of the most useful things we can do with "compctl", is to filter the filename completions based on context. If we can reduce the number of relevant matches in a given situation, we increase the chance of our match being unique, and thus the whole input word can be completed for us. Let's take the use of Java as an example. When we want to compile a Java source file, we typically do something like "javac source.java". The source files always have a ".java" extension, so we can use this information to restrict what Zsh matches against in this situation. Here's how:

zsh% ls
MyPackage  TestProgram.class  TestProgram.html   TestProgram.java
zsh% javac T<TAB>
		[we want to recompile source file]
TestProgram.class  TestProgram.html   TestProgram.java
zsh% javac TestProgram.
                [only matches as far as this]
zsh% javac TestProgram.j<TAB>
		[so we have to type another letter]
zsh% javac TestProgram.java
		[filename is finally complete]
zsh% compctl -g '*.java' javac
		[restrict matches when command is javac]
zsh% javac T<TAB>
zsh% javac TestProgram.java
		[only one match now, completes immediately!]
zsh% ls MyPackage
		[what if we have a package directory?]
Main.class  Main.java
zsh% javac M<TAB>
zsh% javac M<TAB> 
                [oh dear, no match for directory]
zsh% compctl -g '*.java' + -g '*(-/)' javac 
		[expand command to match directories too]
zsh% javac M<TAB>
zsh% javac MyPackage/ 
                [directory now completes]
zsh% javac MyPackage/<TAB>
zsh% javac MyPackage/Main.java
                [only one match inside directory]

The above example shows how we had to refine our "compctl" line to account for directories, otherwise the shell just beeps when we try to complete directory names. But then we have a very powerful mechanism - we were able to enter "MyPackage/Main.java", with just three key presses: "M" followed by the TAB key twice.

Here are a few more example "compctl" lines for your ~/.zshrc file, to get you started:

compctl -g '*.gz' + -g '*(-/)' gunzip gzcat
compctl -g '*(-*)' + -g '*(-/)' strip
compctl -g '*.ps *.eps' + -g '*(-/)' gs ghostview psnup psduplex ps2ascii
compctl -g '*.dvi' + -g '*(-/)' xdvi dvips
compctl -g '*.xpm *.xpm.gz' + -g '*(-/)' xpmroot sxpm pixmap xpmtoppm
compctl -g '*.fig' + -g '*(-/)' xfig
compctl -g '*(-/) .*(-/)' cd
compctl -g '(^(*.o|*.class|*.jar|*.gz|*.gif|*.a|*.Z))' more less vi
compctl -g '*.html' + -g '*(-/)' appletviewer

Most of these should be self-explanatory, as they are similar to the "javac" example, although you may not be familiar with some of the syntax, such as "*(-*)", which matches against executable files, and the "(^..)" form which is a logical NOT operation - in the line above, a number of extensions can be ruled out for commands like "more" and "vi", which usually only operate on text files.

There is one slightly more complicated example which uses a custom function to generate the completions. Going back to our use of "javac", after compiling the source code we probably want to run it, with a command such as "java TestProgram". If we use the default filename completion we'd get as far as "java TestProgram." and the get a list of matches. We can then just remove the "." and we're there. But Zsh can do even better than that. You can give "compctl" the name of a function which returns a list of matches in a variable called "reply" and it will call that function when required, using its output as the list of matches. So we can define a function which lists all ".class" files in the current directory, and then removes the ".class" extension, and returns this new list. Here is such an implementation, and how it can be used:

zsh% ls
MyPackage  TestProgram.class  TestProgram.html   TestProgram.java
zsh% echo $(ls *.class)
		[use command substitution to generate
TestProgram.class
		list of .class files]
zsh% echo ${$(ls *.class)%.class}
		[combine with parameter expansion to
TestProgram
		remove .class extension]
zsh% myclasslist () { reply=(${$(ls *.class)%.class}) }
                [define our own shell function]
zsh% compctl -K myclasslist java
		[use this function with the java command]
zsh% java <TAB>
                [now we can type "java", followed by a
space, then press the TAB key...]
zsh% java TestProgram
                [...there is only one .class file in
		the current directory, so everything is
		completed for us!]


Shell prompts

In the examples so far, we have been using a prompt of "zsh%", which is not terribly informative. Needless to say the Zsh prompts are highly configurable, and can convey a variety of useful information. The default prompt is controlled by the PROMPT variable. Here are some examples:

zsh% PROMPT='%/> '
		[displays the current working directory]
/home/matt/tmp> cd /usr/bin
/usr/bin> cd
/home/matt>
/home/matt> PROMPT='%~> '
		[use short form of current directory]
~> cd /tmp 
		[~ is used for home directory]
/tmp> cd
~> cd /usr/local/bin
/usr/local/bin> LBIN=/usr/local/bin
		[you can give directories names
		to shorten the prompt]
~LBIN> pwd
/usr/local/bin
~LBIN> cd
~> PROMPT='%m> '
		[the first part of the hostname
		(use %M for the full hostname)]
kheldar> PROMPT='[%!]> '
                [current history event number]
[232]> pwd
/home/matt
[233]> cd /tmp
[234]> !232 
		[the event number is useful for 
pwd             using history commands]
/tmp
[235]> 

The Zsh also supports a right-hand prompt, using the RPROMPT variable which supports the same syntax. Some people find a left-hand prompt which changes in length (such as when displaying pathnames) a distraction because it means the position you start entering commands at is constantly changing. However, being able to see the current directory at a glance is very useful - it certainly saves typing "pwd" all the time. So this makes '%~'an ideal setting for RPROMPT. Another problem with having the current directory in prompts is when the pathname gets very long, which doesn't leave much space for typing your command. You will be pleased to know that Zsh deals with this situation gracefully. If your command gets close to the right-hand prompt, that prompt will automagically disappear, leaving all that extra space for your command. In addition, if you then delete your command enough, the prompt will reappear!

Another cool trick for prompts, and a great visual aid, is use of color. Most modern terminal emulators support a limited set of colors, which can be set using control codes. The usual escape sequence is "ESC-[31m", where the "1" specifies the foreground color, and can be any digit from 0 (default) to 9. The escape character can be entered using "\033" if using the echo command, or it can be inserted directly into the shell by pressing Control-V (the quoted-insert function) followed by the Escape key. It will then appear as "^[", but you can check that it is actually a single character by moving the cursor over it - it will skip over the sequence in one go. Another point to be aware of is the need to tell Zsh which characters form escape sequence in a prompt, otherwise it will assume they are all printable characters, and position the cursor incorrectly. This is done by using the "{%...%}" syntax to enclose any escape sequences. Let's illustrate all of this with an example:

zsh% echo '\033[31m hello \033[30m'
		[see if we can change color -
		"1" is red for us (actual colors may vary), and "0" is the default]
hello
      
                
zsh% echo '^[[31m hello ^[[30m' 
                [try with escape sequences -
		use Control-V, then Escape to give "^[" character]
hello
                
zsh% PROMPT='^[[31mzsh%% ^[[30m'
zsh%
            pwd
		[prompt has changed color
/home/matt
		but cursor positioning is wrong]
zsh> PROMPT='%{^[[31m%}zsh%% %{^[[30m%}'
	        [use "%{...%}" to enclose each sequence]
zsh% pwd
		[positioning is then correct]
/home/matt

If you want to set your default prompt to something like this, then it needs to go in your ~/.zshrc file. It is necessary that you insert the actual escape character "^[", instead of the two characters "^"" and "[". In Emacs/XEmacs you can use Control-Q, then Escape, for a quoted insert, and in vi/vim you can use Control-V, then Escape - the same as Zsh. Alternatively you can use the octal form of the control code, "\033", but the PROMPT setting doesn't understand this syntax itself, so you have to use "echo" and command substitution, as shown in the next example.

Combining use of color in this way, with both left- and right-hand prompts, provides lots of visual clues which can improve your interaction. Having the left-hand prompt stand out helps you to focus on the part of the screen where you will type your next command, and it also separates each command from its output. And having two prompts allows more information to be shown, to save you having to type some commonly used commands, such as "pwd". Personally, I have the short hostname as my left-hand prompt, as it is a fixed width for a given login, and it clearly differentiates terminals connected to different hosts, and I have the current working directory as my right-hand prompt, again in a different color. This is illustrated in the example below:

zsh% PROMPT=$(echo '%{\033[31m%}%m>%{\033[30m%}')
kheldar>RPROMPT=$(echo '%{\033[32m%}%~%{\033[30m%}')
kheldar>cd /usr/X11R6/bin                                   ~
kheldar>
/usr/X11R6/bin


Closing comments

I hope you've enjoyed this brief tour of Zsh. Please do try out the examples for yourself, to really get a feel for Zsh. Of course, we have only just touched on some of its possibilities. Zsh has undergone years of development (still continuing today), so its full capabilities are almost endless. Even if you only use the features covered in this article, I hope you'll find the UNIX shell environment a slightly friendlier place to be...

If you are convinced by Zsh, you can make it your default shell through the "User accounts" panel of Linuxconf (or for the die-hards amongst you, by editing /etc/passwd).


Resources

About the author

Matt Chapman graduated with a BSc. in Computer Science from the University of Warwick in 1996. He has since worked as a software engineer in the IBM Centre for Java Technology, in Hursley, UK, and has been an avid Linux and Zsh user for over five years. He can be contacted at mchapman@uk.ibm.com.

Comments



Trademarks

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=11076
ArticleTitle=Curtains up: introducing the Z shell
publish-date=02012001
author1-email=mchapman@uk.ibm.com
author1-email-cc=