zsh on os x

hexley


I used to think people who argued about the merits of different unix shells were, well, blessed with a lot of spare time.  However, I was finally convinced by Gary Kerbaugh, an immensely knowledgable and helpful person, to give it a try.  I decided to do so for a weekend, and found I was unable to go back.  Under OS X v. 10.2.x, tcsh is the default shell.  The other shells that come with OS X by default include bash (the "Bourne Again" and more user-friendly version of the Bourne shell, sh) and zsh.  zsh is probably most like ksh in its syntax, but is actually MORE user-friendly than is tcsh.  (Other shells that you can invoke on OS X include sh and csh, but these are really bash and tcsh.  Take a look.)

If you are comfortable with tcsh, you will likely find zsh at least as user-friendly.  If you like any of the sh-type shells, you will likely find that zsh incorporates all of the programmability of the sh-type shells but has some very nice additional features.

The zsh site does an excellent job of explaining and advocating for the zsh.  There is a new, excellent book available called From Bash to Z Shell, and there is also a very nice Free user guide available as a pdf zsh user guide that you should download and read. 

Gary Kerbaugh has kindly provided a very nice set of startup files for zsh


Building on these, I created my own set of zsh startup files.





Here are some of the things that zsh offers you that tcsh (and maybe other options) do not:



1.  Really nice command-line completions:

If I have a bunch of files that start with aqua*, and I type open aqua and hit the tab key, here is what I see:

zsh-% open aqua<tab>
---- file
aqua_autoinstall_osx.png*    aqua_devtools_osx.png*       aqua_mosflm_osx.png*         aqua_title_crystal_o.png* 
aqua_backups_osx.png*        aqua_eden_osx.png*           aqua_nmr_osx.png*            aqua_title_crystal_osx.png*
aqua_ccp4_osx.png*           aqua_fink_osx.png*           aqua_othersites_osx.png*     aqua_unixlinks_osx.png*   
aqua_cns_osx.png*            aqua_moldisp_osx.png*        aqua_otherstuff_osx.png*     aqua_xwindows_osx.png*
    

But it gets even better.  If I hit the tab key again, it cylces through the various options in the order listed:

open aqua <tab><tab>
<tab><tab><tab><tab>

open aqua_eden_osx.png


also, it is smart enough to use only sensible options.

You aren't limited to filenames.  It can complete just about anything, including user and domain names

zsh-% ssh wgs<tab>  gives me
zsh-% ssh wgscott@

and

zsh-% ssh wgscott@h<tab>  gives me 
ssh wgscott@hydrogen.ucsc.edu

again it can cycle through all the options.  It even does remote file completion with scp, but you have to have passwordless ssh set up first for it to work properly.


2.  You can define useful functions in addition to aliases:

The various sh-shelss, unlike csh and tcsh, allow you to define functions.  A function is essentially like an alias to a zsh shell script that you can call by name, except that it is read into memory when the shell is started.  It is much more versatile than a simple alias as well.  Here is a trivial example (that probably could be an alias):

joinpdf () {
        gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=merged.pdf "$@"
}

This function, called joinpdf, takes one or any number of pdf files and joins them into one catenated pdf called merge.pdf.  Here is another example, a function that invokes vi as a normal user unless one or more files is owned by root, in which case it invokes sudo vi.  I wrote this (with a lot of help from Gary Kerbaugh, again) because I got really tired of opening a root owned file with vi, making a bunch of changes, and then finding I couldn't save the changes.  This would be too complex for a simple alias, and being a function rather than a shell-script, it is invoked a bit more quickly.

vi () {
        LIMIT=$#
        for ((i = 1; i <= $LIMIT; i++ )) do
                eval file="\$$i"
                if [[ -e $file && ! -O $file ]]
                then
                        otherfile=1
                else

                fi
        done
        if [[ $otherfile = 1 ]]
        then
                sudo vi "$@"
        else
                command vi "$@"
        fi
}

3.  Recursive globbing:

eg:

ls **/*whatever.c

finds your forwhatever.c file anywhere in the directory tree.

4.  Glob qualifiers: 

ls *(/)  prints directories and their contents

print  *(/) prints the directory names

ls *(.) lists just files.

Admitedly, you can do stuff in tcsh like

alias dls '\ls -F | grep /"

but this is way more cool and flexible.



Hi Bill,
   The most fun you can have with your clothes on, eh? This is exactly the kind of "advertising" that makes a difference. I can't testify to ease of switching because I had to put together the scripts. It's both great news to occasional shell users and gratifying that it works "out of the box" with my configuration scripts.
   People really have to see zsh in action to fully believe it. As you said the completion mechanism is really a shining star. When you hit the tab key and there are multiple possibilities, it lists the possibilities by category. If you hit the tab key again multiple times, zsh does the completion, cycling  through the possibilities.
   The real shock comes when I want to do something like "ssh root@www.myhost.edu". I don't have a hostname starting with r so if I type "ssh r" and hit the tab key, I get "ssh root@"! It knows there is going to be a host following and adds the "@" after the "root". Then, assuming only one host starting with "w", typing a w and hitting the tab key results in the entire "ssh root@www.myhost.edu". Even better, if  the command is instead scp, the last step above results in "scp root@www.myhost.edu:", with the additional colon at the end, so that you only have to type the filenames. Naturally zsh will complete the local one.
   A really strange thing happened to me this morning. I was creating an alias for p0f (thanks Sean!) and stupidly tried to hide single quotes inside double quotes. It's not an error, the single quotes simply aren't hidden. However, when I listed the alias, I found that zsh had understood what I meant and had fixed it!! It saved it with single quotes, closed that before my single quote, replaced my single quotes with \', and then picked up with the single quoted string again! Is that unbelievable or what?!
   Thus, you can also enjoy the fact that your initial excitement with zsh will continually be rejuvenated! Thanks for  helping me spread the word!
-- Gary Kerbaugh



The best way to learn about zsh is to use it.  This is painless, because you already have it installed on OS X.  It is often available on other platforms as well.  I have also compiled the latest version on OS X and on Solaris and the compilation was painless.  To use it, you can either

A.  Invoke it from tcsh or any other shell by typing zsh -l at the prompt, i.e.,

% zsh -l

The -l (minus el) argument means that it will run through all the login files as if you had invoked it initially.  zsh -f invokes it but explicitly without reading various files.

B.  Create a term file or iTerm bookmark in which it is invoked on startup.

C.  Make it your default shell by editing netinfo.


To best take advantage of what zsh has to offer, it is nice to start with some startup files.  Gary Kerbaugh has a collection of zsh startup files that goes into ~/Library/init/zsh  that you can download.  Building on these, I created my own set of zsh startup files.
 Click here for web site index

Valid XHTML 1.0!