The Z-Shell (ZSH) Lovers' Page

Latest change: Sat Nov 22 21:44:36 CET 2003

An abstract description of the ZSHELL's features.

The ZSH is a great shell (aka "command line interpreter") which can make it a lot easier for beginners. However, even the Linux community has not really discovered it yet even though it has considerable advantages over bash and tcsh and a lot in common with sh and ksh.

This page lists describes some of ZSH's features to give you an idea of what is possible. Hopefully this is an alternative to your reading the fine manual - which, by the way, has almost 19,00 lines on an 80 column terminal for zsh 4.1.1.

have a lot of fun!

Sven Guckes         webpage-zsh-lover(at)
Matthias Kopfermann matthi(at)


The string [zsh] is presented at the start of line to indicate that the following command is entered with a ZSH. It's just a precaution in case other examples are presented with eg the bash or the ksh.

"EOL" stands for "end-of-line".

ZSH Features

Compatibility. The ZSH is based on ksh and also includes some features of bash and tcsh. There are many books on shells and shell scripting so there is lots of information about how this kind of program works and can be handled.

Emulation Modes. For those who already know the basic shells (sh, csh, ksh) there are emulation modes which tune the zsh such that it behaves just like one of them, eg with emulate sh . (see the zshbuiltins manual, section "emulate" ). [link!] Mind you, the emulation is not perfect. But it certainly eases its use if you have used other shells before.

Configurability! The ZSH has over 130 options which allows adaption to the user's preferences and a lot of flexibility.

Modularity. The ZSH is modular. You can leave out all the modules and thus get a shell with a lot less memory than eg bash.

Expandability. There are a lot of expansion commands. This allows to expand command prefixes *and* parameter prefixes, prefixes of files and directory name, as well as expansion on environment variables,

man zshcompsys


[Programmable expansion have been available since bash 2.04 since April 2000. However, the ksh does not have that feature at all.]

Command Completion

Instead of using a lot of extra options you can now adjust the shell's expansions with the use of functions. This also means that you can quickly generate more completions by simply editing existing ones. And ZSH already ships with quite a few completions, eg try xterm -fn <TAB>.

More examples for completions [using the old scheme up to 3.1.5]:
compctl -g '*(/)' cd - this makes "cd" complete on subdirs only.
compctl -k '(`command`)' - this allows to complete on all sorts of useful things, eg complete on email addresses for the mailer or on ftp addresses for the ftp client.
compctl -g '*.class(:r)' java - this will chop off the file extension on completion which is useful eg with the java compiler. (who would otherwise try to open "foo.class.class").

Global Aliases

An alias name will only be expanded when it is the first word within the command. However, a global alias will also be expanded when it occurs elsewhere. Example: alias -g L=' |less' this allows to enter 'L' at the end of a command chain to get the output piped to the "less" pager:

[zsh] cut -d: -f 5 /etc/passwd | sed 's/ .*//' | sort | uniq -c | sort -n L

Filename Exclusions. The filename patterns are good for finding complicated matches, however, there is usually no way to *exclude* some matches. ZSH however allows the '^' to be used as a NOT operator, meaning "match all dirs/files *not* matching the following pattern". For example, ^(bar|foo) would leave out "bar" and "foo" from the list of all dirs+files in the current directory. More specifically, ls ^(*.bz2|*.gz) would list all dirs+files not ending in either ".bz2" or ".gz". ZSH allows to take away matches with the '~' character. This requires setopt extended_glob to be set.

Furthermore, grep foo bar*~(*.gz|*.bz|*.bz2|*.zip|*.Z) searches for "foo" in all files starting with "bar" except those with an extension which indicates that the data has been compressed.

As this list of extensions is already quote long for typing you might want to abbreviate it with a global alias. Make sure that the global aliases have a unique name; It's probably best to use an unusual character as their initial, eg the '§'.

alias -g §k="*~(*.bz2|*.gz|*.tgz|*.zip|*.z)" Then ls -d §nk will list all compressed files.

Use unalias '§k' to get rid of the alias. Mind the ticks around the alias name - because if they were missing then the ZSH would expand them.

There is an alternative to stuffing all functions into one huge setup file: You can put each function into its own file, put them into some directories, point at these with $FPATH, and select their loading by means of autoload function .


man zshmisc

For each variable written in capital letters, eg PATH, there exists a variable by the same name in lowercase letters. If the upperase name is a list of items then the lowercase one contains these items separated by spaces.

[zsh] echo $PATH

[zsh] echo $path
/home/guckes/bin /usr/local/bin /usr/bin /bin /usr/bin/X11 /usr/games

This makes it a lot easier when dealing with such lists:

for dir in $path; do
for> echo $dir
for> done 

Note: When you are inside a "for" loop then the ZSH indicates this by indenting the input line with "for>".

And this makes assigning a value to a variable easier, too:

[zsh] path=( /home/guckes/bin /usr/local/bin /usr/bin /bin /usr/bin/X11 /usr/games )
[zsh] echo $PATH

[zsh] path=(
array> /home/guckes/bin
array> /usr/local/bin
array> /usr/bin
array> /bin
array> /usr/bin/X11
array> /usr/games
array> )

Again, ZSH helps to see the current context with showing a special input prompt, "array>".

You can now easily modify this input by going back to the command and move the cursor into the input, line by line, editing them, or even delete some:


ZLE - the ZSH Line Editor

The input line of shells can be long, and even be *very* long. But these are displayed as *one* long line which gets broken across several lines on screen. A long line simply gets unreadble, very much like program code which is presented in one long line.

The ZSH has a builtin line editor which allows editing across several lines on screen - but *with* EOLs, so it allows to split up the input line into several lines making editing and reading a lot easier for the user. You can move the cursor between the parts of the input line up and down.

See also: man zshzle

Editing Commands

The command insert-last-word inserts the last word from the previous command line. This easily reuses the word without having to type it in again yourself. Simply type 'ESC' followed by '_' (underscore)

[zsh] ls -l
[zsh] ln -s ESC_ .newsrc
[zsh] ln -s .newsrc
[zsh] bindkey | grep insert-last-word
"^[." insert-last-word
"^[_" insert-last-word

See also: man zshzle

print [TODO]

The print command is similar to "echo" - but allows to sort the input and set the output in columns.

Example: Print the list of letters on the keyboard in columns after they are sorted (option "-o").

[zsh] print -o -C 4  q w e r t y u i o p \
      a s d f g h j k l z x c v b n m
a  h  o  v
b  i  p  w
c  j  q  x
d  k  r  y
e  l  s  z
f  m  t
g  n  u

Note that the last row spells "gnu"! :-)

And if you'd liked to check up on the expandos for the prompt then you can use option "-P" for this without the need to change PS1 at all:

Co-Processes [TODO]

The ZSH supports co-processes (just like the ksh). This allows to

Die Zsh unterstützt wie ihr Vorbild -die Ksh- Koprozesse. Koprozesse sind eine Möglichkeit, an Parallelprozesse Daten zu übergeben und Daten zu empfangen. Diese Koprozesse werden allerdings mit coproc eingeleitet und nicht wie bei der Ksh mit |& .
Mit print -p und mit >&p als Umlenkung kann ich Daten an den Prozeß senden und mit read -p und <&p als Umlenkung Daten von diesem Prozeß auslesen. Beispiel:
coproc ed notizen und dann irgendwann ls -l >&p und nach einer Weile vielleicht echo ".\nw " >&p schreibt in einen parallel laufenden Koprozess, den altehrwürdigen Ed, Daten.


typeset [TODO]

The typeset command is like "echo" - but a lot more powerful. It can align text, uppercase/lowercase it,

typeset -L2 name=werteintrag ;print "[$name]" gibt linksformatiert "[we]" aus, die ersten 2 Buchstaben, umrahmt von [].
typeset -lL20 name=WERTEINTRAG;print "[$name]" gibt in Kleinbuchstaben (-l für lower) "[werteintrag ]" aus, wobei 20 - 11 Leerzeichen eingefügt werden. typeset -r RPROMPT=%n schützt den Rechtsprompt vor einem versehentlichen Überschreiben. Die Variable ist jetzt read-only und zsh warnt einen mit "zsh: read-only variable: RPROMPT ". Alle typeset-Befehle können mit einem - statt dem + aufgehoben werden.

See also: man zshbuiltins , section "typeset"

Filename Expansion with Qualifiers

man zshexpn
/Glob Qualifier

Die Ausgabe von Dateinamen kann mittels sogenannter Qualifier spezifiziert werden:
z.B findet print -l *(m-1) nur Dateien, die vor bis zu einem Tag modifiziert wurden.
print -l *(a1) findet Dateien, auf die vor einem Tag zugegriffen wurde.
print -l *(@) findet nur die Links.
ls -doF *(/) findet nur die Verzeichnisse.
chmod 640 *(W) verändert die Rechte aller Dateien, in die jeder schreiben darf. Das ist ja besonders riskant!
grep name *(.)
findet nur noch reine Dateien. Damit ist die unsinnige Meldung "grep: bla: Is a directory" für alle Zeiten vermeidbar.
Um mal wieder an alte Zeiten erinnert zu werden, könnte man auch grep name *(^.@) eingeben.Hiermit werden alle Dateien aufgelistet, mit denen grep nichts anfangen kann, denn ^ ist das Nicht-Zeichen :).
gg() { grep -wE "$1" *(.) | less -r } könnte eine kleine Zsh-Funktion sein, um gezielt nach einem Wort und unter Zuhilfenahme von regulären Ausdrücken zu suchen. Ich persönlich benutze allerdings lieber perl für solche Dinge und habe dafür mg entdeckt.

Qualifier "lowercase"

for i in [A-Z][A-Z]*(.); do mv $i ${i:l} ;done ist ein sehr schneller Befehl, um Dos-Dateien zu Dateien mit kleinen Buchstaben zu machen. Der Qualifier :l (für lowercase) leistet hier ganze Arbeit.
print -l *(Lk+50) gibt Dateien aus, die über 50 Kilobytes groß sind.

=() (NAME?)

Ich kann unter der Zsh komprimierte Email-Pakete lesen, ohne mich um das Entpacken kümmern zu müssen. Das geht z.B. so:
mutt -f =(zcat mailfold*.gz)
In den =() steht die Aktion, die mit einer Datei gemacht wird, es wird dabei eine temporäre Datei erzeugt und mit ihr z.B. mutt -f aufgerufen. Ein anderes Beispiel:

mutt -f =(cat mail1 mail2 mail3 mail4) ruft mutt mit einer temporären Datei, die diese 4 Mailordner enthält, auf.
(mutt ist ein sehr schönes Mail-Programm, das ich empfehlen kann. Mit mutt -f liest mutt nicht aus /var/spool/mail, sondern die Email-Datei, hier mailfolder.gz)
Ein anderes Beispiel: lynxbzgrep() { lynx -force_html -dump =(bzip2 -cd $1) | grep $2) } ermöglicht es, mit lynxbz bzip2-gepackte HTML-Seiten nach einem Begriff zu untersuchen.

Noch viel besser: Statt einer temporären Datei kann man sogar einen Koprozess bequem aufrufen, indem man <() lynx -force_html <( gzip -cd komprimiert.html.gz ) ist kein Problem . Hier wird lynx, was keine komprimierten Dateien lesen kann, mit dem Output von gzip -cd komprimiert.html.gz , besser einer named pipe gespeißt, sehr cool!

Durch intelligente Kommunikation verschiedener Prozeße wird es möglich, daß man zwei Pseudodateien erzeugen und miteinander vergleichen kann: In Shellkreisen wird dies als "named pipe" bezeichnet, die die Zsh indirekt erzeugt.

diff <(zcat erste_datei.gz) <(zcat zweite_datei.gz) vergleicht den Inhalt zweiter komprimierter Dateien miteinander.


Nach Setzung von READNULLCMD=less läßt sich eine Datei mit < datei unter less angucken. Einfach ein < vor die Datei setzen.

Redirection of output to multiple files

Es ist ohne Probleme möglich, an mehrere Dateien die Standardausggabe umzulenken:
ls >datei1 >datei2 >datei3 oder
ls >> datei1 >> datei2

Redirection of input from multiple files

Man kann auch die Standardeingabe von mehreren Dateien empfangen:
less < datei1 < datei2

Redirection to file as well as send on to pipe

Es ist möglich, eine Umlenkung in eine Datei und gleichzeitig an eine Pipe zu bewerkstelligen:
make >logfile | grep Error

(lookup in $PATH) Mit ls -l =emacs kann ich beispielsweise in jedem Verzeichnis gucken, wie groß emacs genau ist. Ich muß nicht mehr den Pfad angeben. Die Zsh guckt für mich im Pfad nach, wenn ich emacs im Pfad habe. Ich kann auch beqüm Dateien, die im Pfad stehen, auf diese Art editieren. jed =genial.c editiert eine C-Funktion, wenn sie im Pfad gefunden werden kann.

Globbing with Recursion

Statt eines sehr expliziten aber umständlich zu tippenden find-Befehls kann under der ZSH ** als rekursives Kürzel verwendet werden. Mit print -l **/*.html finde ich alle Html-Seiten, die in allen Verzeichnissen unterhalb des jetzigen Verzeichnisses vorhanden sind und gebe sie auf je einer Zeile (-l) aus.(**=Rekursion)
print -l **/datei.html sucht die bestimmte Datei in allen vom aktuellen Verzeichnis abgehenden Verzeichnissen und gibt genau sie aus.
print -l **/*.html~datei.html gibt alle Html-Seiten mit Ausnahme von datei.html zeilenweise aus.
grep name **/*.txt sucht in allen Texten unterhalb des gegenwärtigen Verzeichnisses nach Dateien mit Endung .txt.

Builtin Command "vared"

Mit vared Variable kann ich alle Umgebungsvariablen editieren. Das finde ich praktisch, weil ich sofort die Variable erreiche und nicht erst in einer Datei wie .zshrc nach ihr suchen muß. Außerdem wirkt natürlich das Editieren der Variablen sofort.

Builtin Command "dirs"

Der Befehl dirs -v zeigt mir alle Verzeichnisse, in denen ich in einer Sitzung gewesen bin zeilenweise an, wenn ich wähle:
setopt autopushd Ich kann nun mit cd +2 das vorletzte Verzeichnis erreichen.


Mit der Zsh kann ich sehr lange Unterverzeichnisse im Prompt mit einem kurzem oder signifikanteren Namen (named directory) versehen und dann mit ~name aufrufen. Das ist insbesondere dann sehr praktisch, wenn ich in einem sehr entfernten Ordner von / ausgesehen agiere.
Ich habe mir eine kleine Zsh-funktion geschrieben, die mir aus einem langem Verzeichnisnamen einen kurzen erzeugt:
zzz () {

    NAMED_DIRECTORY=$PWD:t; # der Name des jetzigen Verzeichnisses wird
        #       an NAMED_DIRECTORY ohne die Hierarchie übergeben.
        # :t steht für tail.
     eval $NAMED_DIRECTORY=$PWD; # es findet die Setzung eines named directory statt.
    cd ~$NAMED_DIRECTORY; # es wird in das named directory gesprungen.
          # ist mit dem bestehenden Verzeichnis identisch
        # aber der Name ist kürzer im Prompt.

Eine außerdem sehr praktische Möglichkeit besteht darin, daß ich jetzt nicht mehr den ganzen Pfadnamen angeben muß, wenn ich eine Datei verschiebe. z.B. mv datei.1 ~lm1 könnte meine Datei bequem nach /usr/local/man/man1/ verschieben, wenn ich lm1=/usr/local/man/man1 gesetzt haben sollte.
Bei der Prompt-Darstellung gibt es zwei Mölichkeiten: Entweder weißt man am Ende auch noch den letzten Slash zu: info=/usr/local/info/ Hier wird im Prompt der ganze Name dargestellt. cd ~info springt zwar nach /usr/local/info, aber im Prompt steht: /usr/local/info%. Möchte man aber den kurzen Prompt haben, dann muß man so zuweisen:
info=/usr/local/info, also ohne Slash am Ende.


Die Option autocd erlaubt es, nur den Namen eines Ordners anzugeben. Bei Eindeutigkeit wird dann sofort in diesen Ordner gesprungen. z.B. springt bin dann sofort in mein bin-Verzeichnis.


Es gibt keinen Fehler bei Farbprompts wie unter der Bash1-Shell. Da ich selber eine Gliederung meiner Promptinformationen durch Escape-Farbbefehle liebe, ist mir das wichtig.
Bei der Zsh müssen hierzu allerdings die Escape-Befehle von %{ %} eingerahmt werden. Also zum Beispiel:
%{^[[31m%}%~ %{[0m%}.
Ich habe mir, um die Übersicht nicht zu verlieren, Variablen definiert, die die Farben für den Prompt enthalten. RED_PROMPT='%{^[[31m%}' OFF_PROMPT='%{[0m%}'
Jetzt kann ich auf diese Variablen mit $ zugreifen.


Mit RPROMPT läßt sich ein PROMPT auf der rechten Seite definieren:
RPROMPT=%l zeigt mir beispielsweise an, auf welchem Rechner ich mich befinde.
RPROMPT=%n zeigt den Benutzer an.
RPROMPT=%T zeigt die Zeit an.
RPROMPT=%D{%d.%m.%y} zeigt das Datum nach deutscher Dartstellung an.


Selbstverständlich kennt die Zsh auch eine Korrekturmöglichkeit bei falscher Eingabe, die aber nur dann wirksam wird, wenn man das wirklich eingestellt hat. Man setzt in einen solchem Fall einfach:
setopt autocorrect .
Die Korrektur kann durch Setzung eines alias <befehl>=nocorrect <befehl> verhindert werden.


Um darüber informatiert zu werden, wer alles außer mir sich eingeloggt hat gibt es das Kommando watch wie unter der Tcsh.
watch=(notme) listet z.B alle Benutzer auf, die nicht ich sind :)
Hierbei kann das Format der Ausgabe geändert werde:
WATCHFMT='%n eingeloggt auf Terminal %l von %M seit %T '
Wählt man watch=(@vorsicht_ist_angesagt), so werden alle Benutzer aufgeführt, die von dem Server vorsicht_ist_angesagt eingeloggt sind.
Positiv herum kann man so natürlich auch alle Freunde erkennen lassen:
watch=( < ~/.freunde root) liest alle Freunde aus der Datei .friends in das Feld watch zusätzlich zu root ein. So kann man schnell erkennen, wenn sich Freunde einloggen.


Es gibt in der Zsh einen sehr bequemen Wiederholungsbefehl, der von der tcsh abgeschaut ist: repeat . Möchte ich z.B. einen Werbemailer böswillig strafen, dann könnte ich das so machen:
repeat 100 mail -s "spams suck" < flame Dabei sollte allerdings bedacht werden, daß man damit meist harmlose Benutzer trifft, die schlechte Passworte haben und deshalb Räubern auf den Leim gegangen sind.


Rufe ich ein altes Kommando mit !?kommando auf, habe ich die Möglichkeit, vor der erneuten Ausführung zu gucken, ob es sich hierbei auch um das gewünschte Kommando handelt. Ich drücke einfach TAB. Ebenso kann man sich auch die genau betroffenen Dateien eines global wirkenden Befehles (z.B. ls *.html) mit TAB ansehen.


Gerade eben getippte Worte auf der Kommandozeile können mit !# genauer wiedergegeben werden als bei Bash/Tcsh/Ksh. Man gibt einfach an, wo wiederholt werden soll: z.B. echo ein_langes_wort zweites_langes_wort drei !#1-2 schreibt auf den Bildschirm: ein_langes_wort zweites_langes_wort drei ein_langes_wort zweites_langes_wort.


PERIOD=300; periodic funct() fortune -s führt alle 300 Sekunden die Funktion funct aus und gibt eine Zeile vor dem Prompt eine kurze Weisheit weiter. Allerdings muß dazu auch am Prompt ein Befehl eingegeben werden, sonst kann man natürlich nichts auf dem Bildschirm sehen.


Die Zsh kennt den .. Operator von Pascal (und Perl): echo {1..7}"\n Wo ist Microsoft geblieben?" ergibt: 1 2 3 4 5 6 7 Wo ist Microsoft geblieben?


Wie die Bash kann die Zsh auch durch eckige Klammern Ganzzahlen berechnen: echo "17*35" = $[17*35]


Die Zsh hat in ihrer sehr mächtigen HISTORY CONTROL ein run-help Kommando, mit dem zu einem Buffer gezielt Informationen abgerufen werden können. Voreingestellt ist hier der Aufruf der Manpage zu dem Kommando. Verändern könnte man diesen Aufruf, indem man einfach aus alias run-help=man alias run-help=info macht.


Sogenannte User-Widgets erlauben das Einbinden eigener Funktionen an Tastaturbefehle z.B. für folgende Funktion:
wohin () {
  dirs -v
  print -n "Wohin? "
  read WOHIN
  cd +${WOHIN:=0}
kann mit bindkey w wohin auf Control-Ww gelegt werden. Dazu muß vorher zle -N wohin deklariert worden sein, dieses User-Widget wirkt beim nächsten Aufruf der Shell. man_zshall() man zshall und nachfolgendes Deklarieren von zle -N man_zshall kann durch Definition von bindkey  man_zshall nun immer bei ^Z ausgeführt werden.


sched ist ein interner Befehl zum automatischen zeitgebundenen Abarbeiten von Kommandos ähnlich wie bei at. Dabei werden die Kommandos aber nicht ausgeführt und dann als email zugeschickt, sondern gelangen erst einmal in den Puffer und werden beim nächsten Kommando erst auf dem Bildschirm ausgegeben. sched +0:10 sysvbanner "Du mußt jetzt los" führt in 10 Minuten in Großschrift auf dem Bildschirm eine Warnung zum gehen aus, wenn dann das nächste Kommando eingegen wird, sonst erst beim Kommando, wenn das auch Stunden später sein sollte.


The name ZSH derives from Zhong Shao, teaching assistant at Princeton university (now [2004] at Yale). Paul Falstad thought that his login name, "zsh", was a good name for a shell.

Paul released zsh 1.0 to alt.sources on December 16th 1990.


Matthias Kopfermann - matthi(at)
Matthias is the author of the original version of this text. He started writing it on 1997-10-17 - in German, title "die ZSH Liebhaber Seite". [and his last edit of that page apparently was on 2000-10-06.] "I first learned about the ZSH when I read the Linux Gazette. A user presented a very simple solution using the ZSH about the topic 'renaming von uppercase letters of DOS files to Linux'. And on a page which covered the differences of shells I learne that the author said that the ZSH can probably do more than the author knows about himself. This made me curious."

Sven Guckes -
Sven translated this page on 2003-11-09 and 2003-11-22 (gray November days) into English.

Sven Wischnowsky -
Author of the completion code (compctl and compsys) - and then some. "Mann, muss ich mal Zeit gehabt haben ;-)" (man -

Zhong Shao

Sven Guckes