separate module, though heavily dependent on the main shell --- and you've
probably forgotten I mentioned above using `zmodload zsh/mathfunc
' to
load mathematical functions.
Now it's time to look at functions in more detail. The various issues to be discussed are: loading functions, handling parameters, compiling functions, and repairing bike tyres when the rubber solution won't stick to the surface. Unfortunately I've already used so much space that I'll have to skip the last issue, however topical it might be for me at the moment.
Well, you know what happens now. You can define functions on the command line:
fn() { print I am a function }which you call under the name `
fn
'. As you type, the shell knows that
it's in the middle of a function definition, and prompts you until you get
to the closing brace.
Alternatively, and much more normally, you put a file called fn
somewhere in a directory listed in the $fpath
array. At this point,
you need to be familiar with the KSH_AUTOLOAD
option described in the
last chapter. From now on, I'm just going to assume your autoloadable
function files contain just the body of the function, i.e. KSH_AUTOLOAD
is not set. Then the file fn
would contain:
print I am a functionand nothing else.
Recent versions of zsh, since 3.1.6
, set up $fpath
for you. It
contains two parts, although the second may have multiple directories.
The first is, typically, /usr/local/share/zsh/site-functions
,
although the prefix may vary. This is empty unless your system
administrator has put something in it, which is what it's there for.
The remaining part may be either a single directory such as
/usr/local/share/zsh/3.1.9/functions
, or a whole set of directories
starting with that path. It simply depends whether the person
installing zsh wanted to keep all the functions in the same directory,
or have them sorted according to what they do.  However, none of the functions is autoloaded
automatically, so unless you specifically put `autoload ...
' in a
startup file, the shell won't actually take any notice of them. As
you'll see, part of the path is the shell version. This makes it very
easy to keep multiple versions of zsh with functions which use features
that may be different between the two versions. By the way, if these
directories don't exist, you should check $fpath
to see if they are
in some other location, and if you can't find any correspondence between
what's in $fpath
and what's on the disk even when you start the
shell with zsh -f
to suppress loading of startup files, complain to
the system administrator: he or she has either not installed them
properly, or has made /etc/zshenv
stomp on $fpath
, both of which
are thoroughly evil things to do. (/etc/zshrc
, /etc/zprofile
and /etc/zlogin
shouldn't stomp on $fpath
either, of course. In
fact, they shouldn't do very much; that's up to the user.)
One point about autoload
is the `-U
' option. This turns off the
use of any aliases you have defined when the function is actually loaded
--- the flag is remembered with the name of the function for future
reference, rather than being interpreted immediately by the autoload
command. Since aliases can pretty much redefine any command into any
other, and are usually interpreted while a function is being defined or
loaded, you can see that without this flag there is fair scope for
complete havoc.
alias ls='echo Your ls command has been requisitioned.' lsroot() { ls -l / } lsrootThat's not what the function writer intended. (Yes, I know it actually is, because I wrote it to show the problem, but that's not what I meant.) So
-U
is recommended for all standard functions, where you
have no easy way of telling quite what could be run inside.
Recently, the functions for the new completion system (described in
chapter 6) have been changing the fastest. They either begin
with comp
or an underscore, `_
'. If the functions
directory
is subdivided, most of the subdirectories refer to this. There are
various other classes of functions distributed with the shell:
zf
are associated with zftp, a builtin
system for FTP transfers. Traditional FTP clients, ones which don't
use a graphical interface, tend to be based around a set of commands
on a command line --- exactly what zsh is good at. This also makes it
very easy to write macros for FTP jobs --- they're just shell
functions. This is described in the final chapter along with other
modules. It's based around a single builtin, zftp
, which is
loaded from the module zsh/zftp
.
prompt
, which may be in the Prompts
subdirectory, are part of a `prompt themes' system which makes it
easy for you to switch between preexisting prompts. You load it
with `autoload -U promptinit; promptinit
'. Then `prompt -h
'
will tell you what to do next. If you have new completion loaded
(with `autoload -U compinit; compinit
', what else) the arguments
to `prompt
' can be listed with ^D
and completed with a TAB;
they are various sorts of prompt which you may or may not like.
predict-on
and
incremental-complete-word
. These are editor functions; you use them
with
zle -N predict-on bindkey <keystroke> predict-onHere, the
predict-on
function automatically looks back in the history
list for matching lines as you type. You should also bind
predict-off
, which is loaded when predict-on
is first called.
incremental-complete-word
is a fairly simple attempt at showing
possible completions for the current word as you type; it could do with
improving.
Misc
subdirectory. These
are a very mixed bag which you should read to see if you like any. One
of the most useful is zed
, which allows you to edit a small file
(it's really just a simple front-end to vared
). The run-help
file shows you the sort of thing you might want to define for use with
the \eh
(run-help
) keystroke. is-at-least
is a function
for finding out if the version of the shell running is recent enough,
assuming you know what version you need for a given feature. Several of
the other functions refer to the old completion system --- which you
won't need, since you will be reading chapter 6 and using
the new completion system, of course.
If you have your own functions --- and if you use zsh a lot, you almost
certainly will eventually --- it's a good idea to add your own personal
directory to the front of $fpath
, so that everything there takes
precedence over the standard functions. That allows you to override a
completion function very easily, just by copying it and editing it. I tend
to do something like this in my .zshenv
:
[[ $fpath = *pws* ]] || fpath=(~pws/bin/fns $fpath)to protect against the possibility that the directory I want to add is already there, in case I source that startup file again, and there are other similar ways. (You may well find your own account isn't called
pws
, however.)
Chances are you will always want your own functions to be autoloaded. There is an easy way of doing this: put it just after the line I showed above:
autoload ${fpath[1]}/*(:t)The
${fpath[1]}/*
expands to all the files in the directory at the head
of the $fpath
array. The (:t)
is a `glob modifier': applied to a
filename generation pattern, it takes the tail (basename) of all the files
in the list. These are exactly the names of the functions you want to
autoload. It's up to you whether you want the -U
argument here.
I covered local parameters in some detail when I talked about typeset
,
so I won't talk about that here. I didn't mention the other parameters
which are effectively local to a function, the ones that pass down the
arguments to the function, so here is more detail. They work pretty much
identically in scripts.
There are basically two forms. There is the form inherited from Bourne
shell via Korn shell, with the typically uninformative names: $#
,
$*
, $@
and the numerical parameters $1
etc. --- as high a
number as the shell will parse is allowed, not just single digits. Then
there is the form inherited from the C shell: $ARGC
and $argv
.
I'll mainly use the Bourne shell versions, which are far more commonly
used, and come back to some oddities of the C shell variety at the end.
$#
tells you how many arguments were passed to the function, while
$*
gives those arguments as an array. This was the only array
available in the Bourne shell, otherwise there would probably have been a
more obvious way of doing it. To get the size and the number of elements
of the array you don't use ${#*}
and ${*[1]}
etc. (well, you usually
don't --- zsh is typically permissive here), you use $1
, $2
.
Despite the syntax, these are rather like ordinary array elements; if you
refer to one off the end, you will get an empty string, but no error,
unless you have the option NO_UNSET
set. It is this not-quite array
which gets shifted if you use the shift
builtin without an argument:
the old $1
disappears, the old $2
becomes $1
, and so on, while
$#
is reduced by one. If there were no arguments ($#
was zero),
nothing happens.
The form $@
is very similar to $*
, and you can use it in place of
that in most contexts. There is one place where they differ. Let's define
a function which prints the number of its arguments, then the arguments.
args() { print $# $* }Now some arguments. We'll do this for the current shell --- it's a slightly odd idea, that you can set the arguments for what's already running, or that an interactive shell has arguments at all, but nonetheless it's true:
set arguments to the shell print $*sets
$*
and hence prints the message `arguments to the shell
'. We
now pass these arguments on to the function in two different ways:
args $* args $@This outputs
4 arguments to the shell 4 arguments to the shell-- no surprises so far. Remember, too, that zsh doesn't split words on spaces unless you ask it too. So:
% set 'one word' % args $* 1 one word % args $@ 1 one word
Now here's the difference:
% set two words % args "$*" 1 two words % args "$@" 2 two wordsIn quotes,
"$*"
behaves as a normal array, joining the words with
spaces. However, "$@"
doesn't --- it still behaves as if it was
unquoted. You can't see from the arguments themselves in this case, but
you can from the digit giving the number of arguments the function has.
This probably seems pretty silly. Why quote something to have it behave like an unquoted array? The original answer lies back in Bourne shell syntax, and relates to the vexed question of word splitting. Suppose we turn on Bourne shell behaviour, and try the example of a word with spaces again:
% setopt shwordsplit % set 'one word' % args $* 2 one word % args $@ 2 one word % args "$*" 1 one word % args "$@" 1 one wordAha! This time
"$@"
kept the single word with the space intact.
In other words, "$@"
was a slightly primitive mechanism for suppressing
splitting of words, while allowing the splitting of arrays into elements.
In zsh, you would probably quite often use $*
, not "$@"
, safe in
the knowledge that nothing was split until you asked it to be; and if you
wanted it split, you would use the special form of substitution ${=*}
which does that:
% unsetopt shwordsplit % args $* 1 one word % args ${=*} 2 one word(I can't tell you why the `
=
' was chosen for this purpose, except that
it consists of two split lines, or in an assignment it splits two things,
or something.) This works with any parameter, whether scalar or array,
quoted or unquoted.
However, that's actually not quite the whole story. There are times when the shell removes arguments, because there's nothing there:
% set hello '' there % args $* 2 hello thereThe second element of the array was empty, as if you'd typed
2=--- yes, you can assign to the individual positional parameters directly, instead of using
set
. When the array was expanded on the command line,
the empty element was simply missed out altogether. The same happens with
all empty variables, including scalars:
% empty= % args $empty 0But there are times when you don't want that, any more than you want word splitting --- you want all arguments passed just as you gave them. This is another side effect of the
"$@"
form.
% args "$@" 3 hello thereHere, the empty element was passed in as well. That's why you often find
"$@"
being used in zsh when wordsplitting is already turned off.
Another note: why does the following not work like the example with $*
?
% args hello '' there 3 hello thereThe quotes were kept here. Why? The reason is that the shell doesn't elide an argument if there were quotes, even if the result was empty: instead, it provides an empty string. So this empty string was passed as the second argument. Look back at:
set hello '' thereAlthough you probably didn't think about it at the time, the same thing was happening here. Only with the
'
'
did we get an empty string assigned
to $2
; later, this was missed out when passing $*
to the function.
The same difference occurs with scalars:
% args $empty 0 % args "$empty" 1The
$empty
expanded to an empty string in each case. In the first case
it was unquoted and was removed; this is like passing an empty part of
$*
to a command. In the second case, the quotes stopped that from
being removed completely; this is similar to setting part of $*
to an
empty string using ''
.
That's all thoroughly confusing the first time round. Here's a table to try and make it a bit clearer.
| Number of arguments | if $* contains... | (two words) Expression Word | 'one word' on line splitting? | empty string -------------------------------------------------- $* n | 2 1 0 $@ n | 2 1 0 "$*" n | 1 1 1 "$@" n | 2 1 1 | $* y | 2 2 0 $@ y | 2 2 0 "$*" y | 1 1 1 "$@" y | 2 1 1 | ${=*} n | 2 2 0 ${=@} n | 2 2 0 "${=*}" n | 2 2 1 "${=@}" n | 2 2 1On the left is shown the expression to be passed to the function, and in the three right hand columns the number of arguments the function will get if the positional parameters are set to an array of two words, a single word with a space in the middle, or a single word which is an empty string (the effect of `
set -- '
'
') respectively. The second column shows
whether word splitting is in effect, i.e. whether the SH_WORD_SPLIT
option is set. The first four lines show the normal zsh behaviour; the
second four show the normal sh/ksh behaviour, with word splitting turned
on --- only the case where a word has a space in it changes, and then
only when no quotes are supplied. The final four show what happens when
you use the `${=..}
' method to turn on word splitting, for
convenience: that's particularly simple, since it always forces words to
be split, even inside quotation marks.
I would recommend that anyone not wedded to the Bourne shell behaviour use
the top set as standard: in particular, `$*
' for normal array behaviour
with removal of empty items, `"$@"
' for normal array behaviour with
empty items left as empty items, and `"$*"
' for turning arrays into
single strings. If you need word-splitting, you should use `${=*}
' or
`"${=@}"
' for splitting with/without removal of empty items (obviously
there's no counterpart to the quoted-array behaviour here). Then keep
SH_WORD_SPLIT
turned off. If you are wedded to the Bourne shell
behaviour, you're on your own.
It's a bug
There's a bug in handling of the the form ${1+"$@"}
. This looks
rather an arcane and unlikely combination, but actually it is commonly
used to get around a bug in some versions of the Bourne shell (which is
not in zsh): that "$@"
generates a single empty argument if there
are no arguments. The form shown tests whether there is a first
argument, and if so substitutes "$@"
, else it doesn't substitute
anything, avoiding the bug.
Unfortunately, in zsh, when shwordsplit
is set --- which is the time
you usually run across attempts like this to standardise the way
different shells work --- this will actually cause too much
word-splitting. The way the shell is written at the moment, the
embedded "$@"
will force extra splitting on spaces inside the
arguments. So if the first argument is `one word
', and
shwordsplit
is set, ${1+"$@"}
produces two words `one
'
and `word
'.
Oliver Kiddle spotted a way of getting round this which has been adapted for use in the GNU autoconf package: in your initialisation code, have
[ x$ZSH_VERSION != x ] && alias -g '${1+"$@"}'='"$@"'This uses a global alias to turn
${1+"$@"}
wherever it occurs as a
single word into "$@"
which doesn't have the problem. Aliasing
occurs so early in processing that the fact that most of the characters
have a special meaning to the shell is irrelevant; the shell behaves as
if it read in "$@"
. The only catch is that for this to work the
script or function must use exactly the character string
${1+"$@"}
, with no leading or trailing word characters (whitespace,
obviously, or characters which terminate parsing such as `;
' are all
right). Some day, we may fix the underlying bug, but it's not very easy
with the way the parameter substitution code is written at the moment.
Parameters inherited from csh
The final matter is the C-shell syntax. There are two extra variables
but, luckily, there is not much extra in the way of complexity.
$ARGC
is essentially identical to $#
, and $argv
corresponds
to $*
, but is a real array this time, so instead of $1
you have
${argv[1]}
and so on. They use the convention that scalars used by
the shell are all uppercase, while arrays are all lowercase. This
feature is probably the only reason anyone would need these variants.
For example, ${argv[2,-1]}
means all arguments from the second to
the last, inclusive: negative indices count from the end, and a comma
indicates a slice of an array, so that ${argv[1,-1]}
is always the
same as the full array. Otherwise, my advice would be to stick with the
Bourne shell variants, however cryptic they may look at first sight, for
the usual reason that zsh isn't really like the C shell and if you
pretend it is, you will come a cropper sooner or later.
It looks like you're missing "$@"
, but actually you can do that with
"${argv[@]}"
. This, like negative indices and slices, works with all
arrays.
There's one slight oddity with $ARGC
and $argv
, which isn't
really a deliberate feature of the shell at all, but just in case you
run into it: although the values in them are of course local to
functions, the variables $ARGC
and $argv
themselves are
actually treated like global variables. That means if you apply a
typeset -g
command to them, it will affect the behaviour of
$ARGC
and $argv
in all functions, even though they have
different values. It's probably not a good idea to rely on this
behaviour.
nusubsect(Arguments to all commands work the same)
I've been a little tricky here, because I've been talking about two levels
of functions at once: $*
and friends as set in the current function,
or even at the top level, as well as how they are passed down to commands
such as my args
function. Of course, in the second case the same
behaviour applies to all commands, not just functions. What I mean is, in
fn() { cat $* cat "$*" }the `
cat
' command will see the differences in behaviour between the two
calls just as args
would. That should be obvious.
It's not a bug
Let me finally mention again a feature I noted in passing:
1='first argument'sets the first command argument for the current shell or function, independently of any others. People sometimes complain that
1000000='millionth argument'suddenly makes the shell use a lot more memory. That's not a bug at all: you've asked the shell to set the millionth element of an array, but not any others, so the shell creates an array a million elements long with the first 999,999 empty, except for any arguments which were already set. It's not surprising this takes up a lot of memory.
Since version 3.1.7, it has been possible to compile functions to their internal format. It doesn't make the functions run any faster, it just reduces their loading time; the shell just has to bring the function into memory, then it `runs it as it does any other function. On many modern computers, therefore, you don't gain a great deal from this. I have to admit I don't use it, but there are other definite advantages.
Note that when I say `compiled' I don't mean the way a C compiler, say,
would take a file and turn it into the executable code which the
processor understands; here, it's simply the format that the shell
happens to use internally --- it's useless without a suitable version of
zsh to run it. Also, it's no use thinking you can hide your code from
prying eyes this way, like you can to some extent with an ordinary
compiler (disassembling anything non-trivial from scratch being a
time-consuming job): first of all, ordinary command lines appear inside
the compiled files, except in slightly processed form, and secondly
running `functions
' on a compiled function which has been loaded
will show you just as much as it would if the function had been loaded
normally.
One other advantage is that you can create `digest' files, which are sets of functions stored in a single file. If you often use a large fraction of those files, or they are small, or you like the function itself to appear when you run `functions' rather than a message saying it hasn't been loaded, then this works well. In fact, you can compile all the functions in a single directory in one go. You might think this uses a lot of memory, but often zsh will simply `memory map' the file, which means rather than reserving extra main memory for it and reading it in --- the obvious way of reading files --- it will tell the operating system to make the file available as if it were memory, and the system will bring it into memory piece by piece, `paging' the file as it is needed. This is a very efficient way of doing it. Actually, zsh supports both this method and the obvious method of simply reading in the file (as long as your operating system does); this is described later on.
A little extra, in case you're interested: if you read in a file normally, the system will usually reserve space on a disk for it, the `swap', and do paging from there. So in this case you still get the saving of main memory --- this is standard in all modern operating systems. However, it's not as efficient: first of all, you had to read the file in in the first place. Secondly it eats up swap space, which is usually a fixed amount of disk, although if you've got enough main memory, the system probably won't bother allocating swap. Thirdly --- this is probably the clincher for standard zsh functions on a large system --- if the file is directly mapped read-only, as it is in this case, the system only needs one place in main memory, plus the single original file on disk, to keep the function, which is very much more efficient. With the other method, you would get multiple copies in both main memory and (where necessary) swap. This is how the system treats directly executable programmes like the shell itself --- the data is specific to each process, but the programme itself can be shared because it doesn't need to be altered when it's running.
Here's a simple example.
% echo 'echo hello, world' >hw % zcompile hw % ls hw hw.zwc % rm hw % fpath=(. $fpath) % autoload hw % hw hello, worldWe created a simple `hello, world' function, and compiled it. This produces a file called `
hw.zwc
'. The extension stands for `Z-shell
Word Code', because it's based on the format of words (integers longer
than a single byte) used internally by the shell. Then we made sure the
current directory was in our $fpath
, and autoloaded the function,
which ran as expected. We deleted the original file for demonstration
purposes, but as long as the `.zwc
' file is newer, that will be
used, so you don't need to remove the originals in normal use. In fact,
you shouldn't, because you will lose any comments and formatting
information in it; you can regenerate the function itself with the
`functions
' command (try it here), but the shell only remembers the
information actually needed to run the commands. Note that the function
was in the zsh autoload format, not the ksh one, in this case (but see
below).
And there's more
Now some bells and whistles. Remember the KSH_AUTOLOAD
thing? When
you compile a function, you can specify which format --- native zsh or
ksh emulation --- will be used for loading it next time, by using the
option -k
or -z
, instead of the default, which is to examine the
option (as would happen if you were autoloading directly from the file).
Then you don't need to worry about that option. So, for example, you
could compile all the standard zsh functions using `zcompile -z
' and
save people the trouble of making sure they are autoloaded correctly.
You can also specify that aliases shouldn't be expanded when the files
are compiled by using -U
: this has roughly the same effect as saying
autoload -U
, since when the shell comes to load a compiled file, it
will never expand aliases, because the internal format assumes that all
processing of that kind has already been done. The difference in this
case is if you don't specify -U
: then the aliases found when you
compile the file, not when you load the function from it, will be used.
Now digest files. Here's one convenient way of doing it.
% ls ~/tmp/fns hw1 hw2 % fpath=(~/tmp/fns $fpath) % cd ~/tmp % zcompile fns fns/* % ls fns fns.zwcWe've made a directory to put functions in,
~/tmp/fns
, and stuck some
random files in it. The zcompile
command, this time, was given several
arguments: a filename to use for the compiled functions, and then a list of
functions to compile into it. The new file, fns.zwc
, sits in the same
directory where the directory fns
, found in $fpath
, is. The shell
will actually search the digest file instead of the directory. More
precisely, it will search both, and see which is the more recent, and use
that as the function. So now
% autoload hw1 % hw1 echo hello, first world
You can test what's in the digest file with:
% zcompile -t fns zwc file (read) for zsh-3.1.9-dev-3 fns/hw1 fns/hw2Note that the names appear as you gave them on the command line, i.e. with
fns/
in front. Only the basenames are important for
autoloading functions. The note `(read)
' in the first line means
that zsh has marked the functions to be read into the shell, rather than
memory mapped as discussed above; this is easier for small functions,
particularly if you are liable to remove or alter a file which is
mapped, which will confuse the shell. It usually decides which method
to use based on size; you can force memory mapping by giving the -M
option. Memory mapping doesn't work on all systems (currently including
Cygwin).
I showed this for compiling files, but you can actually tell the shell
to output compiled functions --- in other words, it will look along
$fpath
and compile the functions you specify. I find compiling
files easier, when I do it at all, since then I can use patterns to find
them as I did above. But if you want to do it the other way, you should
note two other options: -a
will compile files by looking along
$fpath
, while -c
will output any functions already loaded by the
shell (you can combine the two to use either). The former is
recommended, because then you don't lose any information which was
present in the autoload file, but not in the function stored in memory
---- this is what would happen if the file defined some extra widgets
(in the non-technical sense) which weren't part of the function called
subsequently.
If you're perfectly happy with the shell only searching a digest
file, and not comparing the datestamp with files in the directory, you
can put that directly into your $fpath
, i.e. ~/tmp/fns.zwc
in
this case. Then you can get rid of the original directory, or archive
it somewhere for reuse.
You can compile scripts, too. Since these are in the same format as a
zsh autoload file, you don't need to do anything different from
compiling a single function. You then run (say) script.zwc
by
typing `zsh script
' --- note that you should omit the .zwc
, as
zsh decides if there's a compiled version of a script by explicitly
appending the suffix. What's more, you can run it using `.
' or
`source
' in just the same way (`. script
') --- this means you
can compile your startup files if you find they take too long to run
through; the shell will spot a ~/.zshrc.zwc
as it would any other
sourceable file. It doesn't make much sense to use the memory mapping
method in this case, since once you've sourced the files you never want
to run them again, so you might as well specify `zcompile -R
' to use
the reading (non-memory-mapping) method explicitly.
If you ever look inside a .zwc
file, you will see that the
information is actually included twice. That's because systems differ
about the order in which numbers are stored: some have the least
significant byte first (notably Intel and some versions of Mips) and
some the most significant (notably SPARC and Cambridge Consultants' XAP
processor, which is notable here mainly because I spend my working hours
programming for it --- you can't run zsh on it). Since zsh uses
integers a great deal in the compiled code, it saves them in both
possible orders for ease of use. Why not just save it for the machine
where you compiled it? Then you wouldn't be able to share the files
across a heterogeneous network --- or even worse, if you made a
distribution of compiled files, they would work on some machines, and
not on others. Think how Emacs users would complain if the .elc
files that arrived weren't the right ones. (Worse, think how the vi
users would laugh.) The shell never reads or maps in the version it
doesn't use, however; only extra disk space is used.
A little -Xtra help
There are two final autoloading issues you might want to know about. In
versions of zsh since 3.1.7, you will see that when you run
functions
on a function which is marked for autoload but hasn't yet
been loaded, you get:
afunctionmarkedforautoloadwhichhasntbeenloaded () { # undefined builtin autoload -XU }The `
# undefined
' is just printed to alert you that this was a
function marked as autoloadable by the autoload
command: you can
tell, because it's the only time functions
will emit a comment
(though there might be other `#
' characters around). What's
interesting is the autoload
command with the -X
option. That
option means `Mark me for autoloading and run me straight away'. You
can actually put it in a function yourself, and it will have the same
effect as running `autoload
' on a not-yet-existent
function. Obviously, the autoload
command will disappear as soon as
you do run it, to be replaced by the real contents. If you put this
inside a file to be autoloaded, the shell will complain --- the
alternative is rather more unpalatable.
Note also the -U
option was set in that example: that simply means
that I used autoload
with the -U
option when I originally told
the shell to autoload the function.
There's another option, +X
, the complete opposite of -X
. This
one can only be used with autoload outside the function you're
loading, just as -X
was only meaningful inside. It means `load the
file immediately, but don't run it', so it's a more active (or, as they
say nowadays, since they like unnecessarily long words, proactive) form
of autoload
. It's useful if you want to be able to run the
functions
command to see the function, but don't want to run the
function itself.
Special functions
I'm in danger of simply quoting the manual, but there are various
functions with a special meaning to the shell (apart from TRAP...
functions, which I've already covered). That is, the functions
themselves are perfectly normal, but the shell will run them
automatically on certain occasions if they happen to exist, and silently
skip them if they don't.
The two most frequently used are chpwd
and precmd
. The former
is called whenever the directory changes, either via cd
, or
pushd
, or an AUTO_CD
--- you could turn the first two into
functions, and avoid needing chpwd
but not the last. Here's how to
force an xterm, or a similar windowing terminal, to put the current
directory into the title bar.
chpwd() { [[ -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 }The first line tests that standard output is really a terminal --- you don't want to print the string in the middle of a script which is directing its output to a file. Then we look to see if we have a
sun-cmd
terminal, which has its own sui generis sequence for
putting a string into the title bar, or something which recognises xterm
escape sequences. In either case, the special sequences (a bit like
termcap sequences as discussed for echotc
) are interpreted by the
terminal, and instead of being printed out cause it to put the string in
the middle into the title bar. The string here is `%~
': I added the
-P
option to print
so it would expand prompt escapes. I could
just have used $PWD
, but this way has the useful effect of
shortening your home directory, or any other named directory, into
~
-notation, which is a bit more readable. Of course, you can put
other stuff there if you like, or, if you're really sophisticated, put
in a parameter $HEADER
and define that elsewhere.
If programmes other than the shell alter what appears in the xterm title
bar, you might consider changing that chwpd
function to precmd
.
The function precmd
is called just before every prompt; in this case
it will restore the title line after every command has run. Some people
make the mistake of using it to set up a prompt, but there are enough
ways of getting varying information into a fixed prompt string that you
shouldn't do that unless you have very odd things in your prompt.
It's a big nuisance having to redefine precmd
to alter your prompt
--- especially if you don't know it's there, since then your prompt
apparently magically returns to the same format when you change it.
There are some good reasons for using precmd
, too, but most of them
are fairly specialised. For example, on one system I use it to check if
there is new input from a programme which is sending data to the shell
asynchronously, and if so printing it out onto the terminal. This is
pretty much what happens with job control notification if you don't have
the NOTIFY
option set.
The name precmd
is a bit of a misnomer: preprompt
would have
been better. It usurps the name more logically applied to the function
actually called preexec
, which is run after you finished editing a
command line, but just before the line is executed. preexec
has one
additional feature: the line about to be executed is passed down as an
argument. You can't alter what's going to be executed by editing the
parameter, however: that has been suggested as an upgrade, but it would
make it rather easy to get the shell into a state where you can't
execute any commands because preexec
always messes them up. It's
better, where possible, to write function front-ends to specific
commands you want to handle specially. For example, here's my ls
function:
local ls if [[ -n $LS_COLORS ]]; then ls=(ls --color=auto) else ls=(ls -F) fi command $ls $*This handles GNU and non-GNU versions of ls. If
$LS_COLORS
is set, it
assumes we are using GNU ls, and hence colouring (or colorizing, in
geekspeak) is available. Otherwise, it uses the standard option -F
to
show directories and links with a special symbol. Then it uses command
to run the real ls
--- this is a key thing to remember any time you use
a function front-end to a command. I could have done this another way:
test in my initialisation files which version of ls
I was using, then
alias ls
to one of the two forms. But I didn't.
Apart from the trap functions, there is one remaining special function.
It is periodic
, which is executed before a prompt, like precmd
,
but only every now and then, in fact every $PERIOD
seconds; it's up
to you to set $PERIOD
when you defined periodic
. If $PERIOD
isn't set, or is zero, nothing happens. Don't get $PERIOD
confused
with $SECONDS
, which just counts up from 0 when the shell starts.
Aliases are much simpler than functions. In the C shell and its
derivatives, there are no functions, so aliases take their place and can
have arguments, which involve expressions rather like those which extract
elements of previous history lines with `!
'. Zsh's aliases, like
ksh's, don't take arguments; you have to use functions for that. However,
there are things aliases can do which functions can't, so sometimes you end
up using both, for example
zfget() { # function to retrieve a file by FTP, # using globbing on the remote host } alias zfget='noglob zfget'The function here does the hard work; this is a function from the zftp function suite, supplied with the shell, which retrieves a file or set of files from another machine. The function allows patterns, so you can retrieve an entire directory with `
zfget *
'. However, you need to
avoid the `*
' being expanded into the set of files in the current
directory on the machine you're logged into; this is where the alias comes
in, supplying the `noglob
' in front of the function. There's no way of
doing this with the function alone; by the time the function is called, the
`*
' would already have been expanded. Of course you could quote it,
but that's what we're trying to avoid. This is a common reason for using
the alias/function combination.
Remember to include the `=
' in alias definition, necessary in zsh,
unlike csh and friends. If you do:
alias zfget noglob zfgetthey are treated as a list of aliases. Since none has the `
=
' and a
definition, the shell thinks you want to list the definitions of the listed
words; I get the output
zfget='noglob zfget' zfget='noglob zfget'since
zfget
was aliased as before, but noglob
wasn't aliased and
was skipped, although the failed alias lookup caused status 1 to be
returned. Remember that the alias
command takes as many arguments as
you like; any with `=
' is a definition, any without is a request to
print the current definition.
Aliases can in fact be allowed to expand to almost anything the shell understands, not just sets of words. That's because the text retrieved from the alias is put back into the input, and reread more or less as if you'd typed it. That means you can get away with strange combinations like
alias tripe="echo foo | sed 's/foo/bar/' |" tripe catwhich is interpreted exactly the same way as
echo foo | sed 's/foo/bar/' | catwhere the word `
foo
' is sent to the stream editor, which alters it to
`bar
' (`s/old/new/
' is sed
's syntax for a substitution), and
passes it on to `cat
', which simply dumps the output. It's useless, of
course, but it does show what can lurk behind an apparently simple command
if it happens to be an alias. It is usually not a good idea to do this,
due to the potential confusion.
As the manual entry explains, you can prevent an alias from being expanded
by quoting it. This isn't like quoting any other expansion, though;
there's no particular important character which has to be interpreted
literally to stop the expansion. The point is that because aliases are
expanded early on in processing of the command line, looking up an alias is
done on a string without quotes removed. So if you have an alias
`drivel
', none of the strings `\drivel
', `'d'rivel
', or
`drivel""
' will be expanded as the alias: they all would have the same
effect as proper commands, after the quotes are removed, but as aliases
they appear different. The manual entry also notes that you can actually
make aliases for any of these special forms, e.g. `alias '\drivel'=...
'
(note the quotes, since you need the backslash to be passed down to the
alias command). You would need a pretty good reason to do so.
Although my `tripe
' example was silly, you know from the existence of
`precommand modifiers' that it's sometimes useful to have a special command
which precedes a command line, like noglob
or the non-shell command
nice
. Since they have commands following, you would probably expect
aliases to be expanded there, too. But this doesn't work:
% alias foo='echo an alias for foo' % noglob foo zsh: command not found: foobecause the
foo
wasn't in command position. The way round this is to
use a special feature: aliases whose definitions end in a space force the
next word along to be looked up as a possible alias, too:
% alias noglob='noglob ' % noglob foo an alias for foowhich is useful for any command which can take a command line after it. This also shows another feature of aliases: unlike functions, they remember that you have already called an alias of a particular name, and don't look it up again. So the `
noglob
' which comes from expanding the
alias is not treated as an alias, but as the ordinary precommand modifier.
You may be a little mystified about this difference. A simple answer is that it's useful that way. It's sometimes useful for functions to call themselves; for example if you are handling a directory hierarchy in one go you might get a function to examine a directory, do something for every ordinary file, and for every directory file call itself with the new directory name tacked on.  Another answer is that it's particularly easy to mark aliases as being `in use' while they are being expanded, because it happens while the strings inside them are being examined, before any commands are called, where things start to get complicated.
Lastly, there are `global aliases'. If aliases can get you into a lot of
trouble, global aliases can get you into a lot of a lot of trouble. They
are defined with the option -g
and are expanded not just in command
position, but anywhere on the command line.
alias -g L='| less' echo foo LThis turns into `
echo foo | less
'.
I already pointed out that alias lookups are done so early that aliases are expanded when you define functions:
% alias hello='echo I have been expanded' % fn() { function> hello function> } % which fn fn () { echo I have been expanded }You can't stop this when typing in functions directly, except by quoting part of the name you type. When autoloading, the
-U
option is
available, and recommended for use with any non-trivial function.
A brief word about that `function>
' which appears to prompt you while
you are editing a function; I mentioned this in the previous chapter but
here I want to be clearer about what's going on. While you are being
prompted like that, the shell is not actually executing the commands you
are typing in. Only when it is satisfied that it has a complete set of
commands will it go away and execute them (in this case, defining the
function). That means that it won't always spot errors until right at the
end. Luckily, zsh has multi-line editing, so if you got it wrong you
should just be able to hit up-arrow and edit what you typed; hitting return
will execute the whole thing in one go. If you have redefined $PS2
(or
$PROMPT2
), or you have an old version of the shell, you may not see the
full prompt, but you will usually see something ending in `>
' which
means the same.
As a reminder, the shell looks up commands in this order:
unalias
',
unfunction
' them,
builtin
' in front,
command
' in front.
As I keep advertising, there will be a whole chapter dedicated to the subject of shell expansions and what to do with them. However, it's a rather basic subject, which definitely comes under the heading of basic shell syntax, so I shall here list all the forms of expansion. As given in the manual, there are five stages.
This is the earliest, and is only done on an interactive command line, and
only if you have not set NO_BANG_HIST
. It was described in the section
`The history mechanism; types of history' in the previous chapter.
It is almost independent of the shell's processing of the command line; it
takes place as the command line is read in, not when the commands are
interpreted. However, in zsh it is done late enough that the `!
's can
be quoted by putting them in single quotes:
echo 'Hello!!'doesn't insert the previous line at that point, but
echo "Hello!!"does. You can always quote active `
!
's with a backslash, so
echo "Hello\!\!"works, with or without the double quotes. Amusingly, since single quotes aren't special in double quotes, if you set the
HIST_VERIFY
option,
which puts the expanded history line back on the command line for possible
further editing, and try the first two of the three possibilities above in
order, then keep hitting return, you will find ever increasing command
lines:
% echo 'Hello!!' Hello!! % echo "Hello!!" % echo "Helloecho 'Hello!!'" % echo "Helloecho 'Helloecho 'Hello!!''" % echo "Helloecho 'Helloecho 'Helloecho 'Hello!!'''"and if you understand why, you have a good grasp of how quotes work.
There's another way of quoting exclamation marks in a line: put a `!"
'
in it. It can appear anywhere (as long as it's not in single quotes) and
will be removed from the line, but it has the effect of disabling any
subsequent exclamation marks till the end of the line. This is the only
time quote marks which are significant to the shell (i.e. are not
themselves quoted) don't have to occur in a matching pair.
Note that as exclamation marks aren't active in any text read
non-interactively --- and this includes autoloaded functions and sourced
files, such as startup files, read inside interactive shells --- it is an
error to quote any `!
's in double quotes in files. This will simply
pass on the backslashes to the next level of parsing. Other forms of
quoting are all right: `\!
', because any character quoted with a
backslash is treated as itself, and '!'
because single quotes can quote
anything anyway.
As discussed above, alias expansion also goes on as the command line is
read, so is to a certain extent similar to history expansion. However,
while a history expansion may produce an alias for expansion, `!
's in
the text resulting from alias expansions are normal characters, so it can
be thought of as a later phase (and indeed it's implemented that way).
There are a whole group of expansions which are done together, just by
looking at the line constructed from the input after history and alias
expansion and reading it from left to right, picking up any active
expansions as the line is examined. Whenever a complete piece of
expandable text is found, it is expanded; the text is not re-examined,
except in the case of brace expansion, so none of these types of expansion
is performed on any resulting text. Whether later forms of expansion ---
in other words, filename generation and filename expansion are performed
--- is another matter, depending largely on the GLOB_SUBST
option as
discussed in the previous chapter. Here's a brief summary of the different
types.
Process substitution
There are three forms that result in
a command line argument which refers to a file from or to which
input or output is taken: `<
(process)'
runs the process which is expected to generate output which can be used
as input by a command; `>
(process)' runs the
process which will take input to it; and
`=
(process)' acts like the first one, but it is
guaranteed that the file is a plain file.
This probably sounds like gobbledygook. Here are some simple examples.
cat < <(echo This is output)(There are people in the world with nothing better to do than compile lists of dummy uses of the `
cat
' command, as in that example, and pour scorn
on them, but I'll just have to brave it out.) What happens is that the
command `echo This is output
' is run, with the obvious result. That
output is not put straight into the command line, as it would be with
command substitution, to be described shortly. Instead, the command line
is given a filename which, when read, gets that output. So it's more like:
echo This is output >tmpfile cat < tmpfile rm tmpfile(note that the temporary file is cleaned up automatically), except that it's more compact. In this example I could have missed out the remaining `
<
', since cat
does the right thing with a filename, but I put it
there to emphasise the fact that if you want to redirect input from the
process substitution you need an extra `<
', over and above the one
in the substitution syntax.
Here's an example for the corresponding output substitution:
echo This is output > \ >(sed 's/output/rubbish/' >outfile)which is a perfectly foul example, but works essentially like:
echo This is output >tmpfile sed 's/output/rubbish/' <tmpfile >outfileThere's an obvious relationship to pipes here, and in fact this example could be better written,
echo This is output | sed 's/output/rubbish/' >outfileA good example of an occasion where the output process substitution can't be replaced by a pipe is when it's on the error output, and standard output is being piped:
./myscript 2> >(grep -v idiot >error.log) | process-output >output.loga little abstract, but here the main point of the script `myscript' is to produce some output which undergoes further processing on the right-hand side of the pipe. However, we want to process the error output here, by filtering out occurrences of lines which use the word `idiot', before dumping those errors into a file
error.log
. So we get an effect
similar to having two pipelines at once, one for output and one for error.
Note again the two `>
' signs present next to one another to get
that effect.
Finally, the `=
(process)' form. Why do we need this
as well as the one with `<
'? To understand that, you need to know a
little of how zsh tries to implement the latter type efficiently. Most
modern UNIX-like systems have `named pipes', which are essentially files
that behave like the `|
' on the command line: one process writes to
the file, another reads from it, and the effect is essentially that data
goes straight through. If your system has them, you will usually find the
following demonstration works:
% mknod tmpfile p % echo This is output >tmpfile & [2] 1507 % read line <tmpfile % [2] + 1507 done echo This is output >> tmpfile % print -- $line This is output %The syntax to create a named pipe is that rather strange `
mknod
'
command, with `p
' for pipe. We stick this
in the background, because it won't do anything yet: you can't write to
the pipe when there's no-one to read it (a fundamental rule of pipes which
isn't quite as obvious as it may seem, since it is possible for
data to lurk in the pipe, buffered, before the process reading from it
extracts it), so we put that in the background to wait for action. This
comes in the next line, where we read from the pipe: that allows the
echo
to complete and exit. Then we print out the line we've read.
The problem with pipes is that they are just temporary storage spaces for
data on the way through. In particular, you can't go back to the beginning
(in C-speak, `you can't seek backwards on a pipe') and re-read what was
there. Sometimes this doesn't matter, but some commands, such as editors,
need that facility. As the `<
' process substitution is implemented with
named pipes (well, maybe), there is also the `=
' form, which produces a
real, live temporary file, probably in the `/tmp
' directory, containing
the output from the file, and then puts the name of that file on the
command line. The manual notes, unusually helpfully, that this is useful
with the `diff
' command for comparing the output of two processes:
diff =(./myscript1) =(./myscript2)where, presumably, the two scripts produce similar, but not identical, output which you want to compare.
I said `well, maybe' in that paragraph because there's another way zsh can
do `<
' process substitutions. Many modern systems allow you to access
a file with a name like `/dev/fd/0
' which corresponds to file
descriptor 0, in this case standard input: to anticipate the section on
redirection, a `file descriptor' is a number assigned to a particular input
or output stream. This method allows you to access it as a file; and if
this facility is available, zsh will use it to pass the name of the file in
process substitution instead of using a named pipe, since in this case it
doesn't have to create a temporary file; the system does everything. Now,
if you are really on the ball, you will realise that this doesn't get
around the problem of pipes --- where is data on this file descriptor going
to come from? The answer is that it will either have to come from a real
temporary file --- which is pointless, because that's what we wanted to
avoid --- or from a pipe opened from some process --- which is equivalent
to the named pipe method, except with just a file descriptor instead of a
name. So even if zsh does it this way, you still need the `=
' form for
programmes which need to go backwards in what they're reading.
Parameter substitution
You've seen enough of this already. This comes from a `$
' followed
either by something in braces, or by alphanumeric characters forming the
name of the parameter: `$foo
' or `${foo}
', where the second form
protects the expansion from any other strings at the ends and also allows
a veritable host of extra things to appear inside the braces to modify the
substitution. More detail will be held over to till
chapter 5; there's a lot of it.
Command substitution
This has two forms, $
(process) and
`
process`
. They function identically; the first form has two
advantages: substitutions can be nested, since the end character is
different from the start character, and (because it uses a `$
') it
reminds you that, like parameter substitutions, command substitutions can
take place inside double-quoted strings. In that case, like most other
things in quotes, the result will be a single word; otherwise, the result
is split into words on any field separators you have defined, usually
whitespace or the null character. I'll use the args
function again:
% args() { print $# $*; } % args $(echo two words) 2 two words % args "$(echo one word)" 1 one wordThe first form will split on newlines, not just spaces, so an equivalent is
% args $(echo two; echo words) 2 two wordsThus entire screens of text will be flattened out into a single line of single-word command arguments. By contrast, with the double quotes no processing is done whatsoever; the entire output is put verbatim into one command argument, with newlines intact. This means that the quite common case of wanting a single complete line from a file per command argument has to be handled by trickery; zsh has such trickery, but that's the stuff of chapter 5.
Note the difference from process substitution: no intermediate file name
is involved, the output itself goes straight onto the command line. This
form of substitution is considerably more common, and, unlike the other, is
available in all UNIX shells, though not in all shells with the more modern
form `$
(...
)'.
The rule that the command line is evaluated only once, left to right, is
adhered to here, but it's a little more complicated in this case since the
expression being substituted is scanned as a complete command line, so
can include anything a command usually can, with all the rules of quoting
and expansion being applied. So if you get confused about what a command
substitution is actually up to, you should extract the commands from it and
think of them as a command line in their own right. When you've worked out
what that's doing, decide what it's output will be, and that's the result
of the substitution. You can ignore any error output; that isn't captured,
so will go straight to the terminal. If you want to ignore it, use the
standard trick (see below) `2>/dev/null
' inside the command
substitution --- not on the main command line, where it won't work because
substitutions are performed before redirection of the main command line,
and in any case that will have the obvious side effect of changing the
error output from the command line itself.
The only real catch with command substitution is that, as it is run as a separate process --- even if it only involves shell builtins --- no effects other than the output will percolate back to the main shell:
% print $(bar=value; print bar is $bar) bar is value % print bar is $bar bar isThere is maybe room for a form of substitution that runs inside the shell, instead; however, with modern computers the overhead in starting the extra process is pretty small --- and in any case we seem to have run out of new forms of syntax.
Once you know and are comfortable with command substitution, you will probably start using it all the time, so there is one good habit to get into straight away. A particularly common use is simply to put the contents of a file onto the command line.
# Don't do this, do the other. process_cmd `cat file_arguments`But there's a shortcut.
# Do do this, don't do the other process_cmd $(<file_arguments)It's not only less writing, it's more efficient: zsh spots the special syntax, with the
<
immediately inside the parentheses, reads the file
directly without bothering to start `cat
', and inserts its contents:
no external process is involved. You shouldn't confuse this with `null
redirections' as described below: the syntax is awfully similar,
unfortunately, but the feature shown here is not dependent on that other
feature being enabled or set up in a particular way. In fact, this feature
works in ksh, which doesn't have zsh's null redirections.
You can quote the file-reading form too, of course: in that case, the
contents of the file `cmd_arguments
' would be passed as just one
argument, with newlines and spaces intact.
Sometimes, the rule about splitting the result of a command substitution can get you into trouble:
% typeset foo=`echo words words` % print $foo wordsYou probably expected the command substitution not to be split here. but it was, and the shell executed typeset with the arguments `
foo=words
' and `words'. That's because in zsh arguments to
typeset
are treated pretty much normally, except for some jiggery
pokery with tildes described below. Other shells do this differently, and
zsh (from 4.0.2 and 4.1.1) provides a compatibility option,
KSH_TYPESET
. In earlier versions you need to use
quotes:
% typeset foo="`echo words words`" % print $foo words words
A really rather technical afterword: using `$(cat file_arguments)
', you
might have counted two extra processes to be started, one being the usual
one for a command substitution, and another the `cat
' process, since
that's an external command itself. That would indeed be the obvious way of
doing it, but in fact zsh has an optimisation in cases like this: if it
knows the shell is about to exit --- in this case, the forked process which
is just interpreting the command line for the substitution --- it will not
bother to start a new process for the last command, and here just replaces
itself with the cat
. So actually there's only one extra process here.
Obviously, an interactive shell is never replaced in this way, since
clairvoyance is not yet a feature of the shell.
Arithmetic substitution
Arithmetic substitution is easy to explain: everything I told you about
the (( ... ))
command under numerical parameters, above, applies to
arithmetic substitution. You simply bang a `$
' in front, and it becomes
an expansion.
% print $(( 32 + 2 * 5 )) 42You can perform everything inside arithmetic substitution that you can inside the builtin, including assignments; the only difference is that the status is not set, instead the value is put directly onto the command line in place of the original expression. As in C, the value of an assignment is the value being assigned, `
$(( param = 3 + 2))
'
substitutes the value 5 as well as assigning it to $param
.
By the way, there's an extra level of substitution involved in all arithmetic expansions, since scalar parameters are subject to arithmetic expansion when they're read in. This is simple if they only contain numbers, but less obvious if they contain complete expressions:
% foo=3+5 % print $(( foo + 2)) 10The foo was evaluated into 8 before it was substituted in. Note this means there were two evaluations: this doesn't work:
% foo=3+ % print $(( foo 2 )) zsh: bad math expression: operand expected at `'--- the complaint here is about the missing operand after the `
+
' in
the $foo
. However the following does work:
% foo=3+ % print $(( $foo 2 )) 5That's because the scalar
$foo
is turned into 3+
first. This is
more logical than you might think: with the rule about left to right
evaluation, the $foo
is picked up inside the $((...))
and expanded
as an ordinary parameter substitution while the argument of $((...))
is being scanned. Then the complete argument `3+ 2
' is expanded as an
arithmetical expression. (Unfortunately, zsh isn't always this logical;
there could easily be cases where we haven't thought it through --- you
should feel free to bring these to our attention.)
There's an older form with single square brackets instead of double parentheses; there is now no reason to use it, as it's non-standard, but you may sometimes still meet it.
Brace expansion
Brace expansion is a feature acquired from the C shell and it's relatives, although some versions of ksh have it, as it's a compile time option there. It's a useful way of saving you from typing the same thing twice on a single command line:
% print -l {foo,bar}' is used far too often in examples' foo is used far too often in examples bar is used far too often in examples`
print
' is given two arguments which it is told to print out one per
line. The text in quotes is common to both, but one has `foo
' in
front, while the other has `bar
' in front. The brace expression can
equally be in the middle of an argument: for example, a common use of this
among programmers is for similarly named source files:
% print zle_{tricky,vi,word}.c zle_tricky.c zle_vi.c zle_word.cAs you see, you're not limited to two; you can have any number. You can quote a comma if you need a real one:
% print -l \`{\,,.}\'' is a punctuation character' `,' is a punctuation character `.' is a punctuation characterThe quotes needed quoting with a backslash to get them into the output. The second comma is the active one for the braces.
You can nest braces. Once again, this is done left to right. In
print {now,th{en,ere{,abouts}}}the first argument of the outer brace is `
now
', and the second is
`th{en,ere{,abouts}}
'. This brace expands to `then
' and then the
expansion of `there{,abouts}
', which is `there thereabouts
' ---
there's nothing to stop you having an empty argument. Putting this all
together, we have
print now then there thereabouts
There's more to know about brace expansion, which will appear in chapter 5 on clever expansions.
It's a shame the names `filename expansion' and `filename generation' sound
so similar, but most people just refer to `~
and =
expansion' and
`globbing' respectively, which is all that is meant by the two. The first
is by far the simpler. The rule is: unquoted `~
's at the beginning of
words perform expansion of named directories, which may be your home
directory:
% print ~ /home/pwssome user's home directory:
% print ~root /root(that may turn up `
/
' on your system), a directory named directly by you:
% t=/tmp % print ~t /tmpa directory you've recently visited:
% pwd /home/pws/zsh/projects/zshguide % print ~+ /home/pws/zsh/projects/zshguide % cd /tmp % print ~- /home/pws/zsh/projects/zshguideor a directory in your directory stack:
% pushd /tmp % pushd ~ % pushd /var/tmp % print ~2 /tmpThese forms were discussed above. There are various extra rules. You can add a `
/
' after any of them, and the expansions still take place, so
you can use them to specify just the first part of a longer expression (as
you almost certainly have done with a simple `~
'). If you quote the
`~
' in any of the ways quoting normally takes place, the expansion
doesn't happen.
A ~
in the middle of the word means something completely different, if
you have the EXTENDED_GLOB
option set; if you don't, it doesn't mean
anything. There are a few exceptions here; assignments are a fairly
natural one:
% foo=~pws % print $foo /home/pws(note that the `
~pws
', being unquoted, was expanded straight away at
the assignment, not at the print statement). But the following works too:
% PATH=$PATH:~pws/binbecause colons are special in assignments. Note that this happens even if the variable isn't a colon-separated path; the shell doesn't know what use you're going to make of all the different variables.
The companion of `~
' is `=
', which again has to occur at the start
of a word or assignment to be special. The remainder of the word (here the
entire remainder, because directory paths aren't useful) is taken as
the name of an external command, and the word is expanded to the complete
path to that command, using $PATH
just as if the command were to be
executed:
% print =ls /bin/lsand, slightly confusingly,
% foo==ls % print $foo /bin/lswhere the two `
=
's have two different meanings. This form is useful
in a number of cases. For example, you might want to look at or edit a
script which you know is in your path; the form
% vi =scriptnameis more convenient than the more traditional
% vi `whence -p ls`where I put the `
-p
' in to force whence
to follow the path,
ignoring builtins, functions, etc. This brings us to another use for
`=
' expansion,
% =lsis a neat and extremely short way of referring to an external command when
ls
is usually a function. It has some of the same effect
as `command ls
', but is easier to type.
In versions up to and including 4.0
, this syntax will also expand
aliases, so you need to be a bit careful if you really want a path to an
external command:
% alias foo='ls -F' % print =foo ls -F(Path expansion is done in preference, so you are safe if you use
ls
, unless your $PATH
is strange.) Putting `=foo
' at the
start of the command line doesn't work, and the reason why bears
examination: =
-expansion occurs quite late on, after ordinary alias
expansion and word splitting, so that the result is the single word
`ls -F
', where the space is part of the word, which probably doesn't
mean anything (and if it does, don't lend me your computer when I need
something done in a hurry). It's probably already obvious that alias
expansion here is more trouble than it's worth. A less-than-exhaustive
search failed to find anyone who liked this feature, and it has been
removed from the shell from 4.1, so that `=
'-expansion now only
expands paths to external commands.
If you don't like =
-expansion, you can turn it off by setting the
option NO_EQUALS
. One catch, which might make you want to do that, is
that the commands mmv
, mcp
and mln
, which are a commonly used
though non-standard piece of free software, use `=
' followed by a
number to replace a pattern, for example
mmv '*.c' '=1.old.c'renames all files ending with
.c
to end with .old.c
. If you were
not alert, you might forget to quote the second word. Otherwise, however,
=
' isn't very common at the start of a word, so you're probably fairly
safe. For a way to do that with zsh patterns, see the discussion of
the function zmv
below (the answer is `zmv '(*).c' '$1.old.c'
').
Note that zsh is smart enough to complete the names of commands after an
`=
' of the expandable sort when you hit TAB.
Filename generation is exactly the same as `globbing': the expanding of any unquoted wildcards to match files. This is only done in one directory at a time. So for example
print *.cwon't match files in a subdirectory ending in `
.c
'. However, it is
done on all parts of a path, so
print */*.cwill match all `
.c
' files in all immediate subdirectories of the
current directory. Furthermore, zsh has an extension --- one of its most
commonly used special features --- to match files in any subdirectory at
any depth, including the current directory: use two `*
's as part of the
path:
print **/*.cwill match `
prog.c
', `version1/prog.c
',
`version2/test/prog.c
', `oldversion/working/saved/prog.c
', and
so on. I will talk about filename generation and other uses of zsh's
extremely powerful patterns at much greater length in
chapter 5. My main thrust here is to fit it into other forms
of expansion; the main thing to remember is that it comes last, after
everything has already been done.
So although you would certainly expect this to work,
print ~/*generating all files in your home directory, you now know why: it is first expanded to `
/home/pws/*
' (or wherever), then the shell scans down the
path until it finds a pattern, and looks in the directory it has reached
(/home/pws
) for matching files. Furthermore,
foo=~/ print $foo*works. However, as I explained in the last chapter, you need to be careful with
foo=* print ~/$fooThis just prints `
/home/pws/*
'. To get the `*
' from the parameter
to be a wildcard, you need to tell the shell explicitly that's what you
want:
foo=* print ~/${~foo}As also noted, other shells do expand the
*
as a wildcard anyway. The
zsh attitude here, as with word splitting, is that parameters should do
exactly what they're told rather than waltz off generating extra words or
expansions.
Be even more careful with arrays:
foo=(*)will expand the
*
immediately, in the current directory --- the
elements of the array assignment are expanded exactly like a normal command
line glob. This is often very useful, but note the difference from scalar
assignments, which do other forms of expansion, but not globbing.
I'll mention a few possible traps for the unwary, which might confuse you until you are a zsh globbing guru. Firstly, parentheses actually have two uses. Consider:
print (foo|bar)(.)The first set of parentheses means `match either
foo
or bar
'. If
you've used egrep
, you will probably be familiar with this. The
second, however, simply means `match only regular files'. The `(.)
' is
called a `globbing qualifier', because it limits the scope of any matches so
far found. For example, if either or both of foo
and bar
were
found, but were directories, they would not now be matched. There are many
other possibilities for globbing qualifiers. For now, the easiest way to
tell if something at the end is not a globbing qualifier is if it
contains a `|
'.
The second point is about forms like this:
print file-<1-10>.datThe `
<
' and `>
' smell of redirection, as described next, but
actually the form `<
', optional start number, `-
', optional finish
number, `>
' means match any positive integer in the range between the
two numbers, inclusive; if either is omitted, there is no limit on that
end, hence the cryptic but common `<->
' to match any positive integer
--- in other words, any group of decimal digits (bases other than ten are
not handled by this notation). Older versions of the shell allowed the
form `<>
' as a shorthand to match any number, but the overlap with
redirection was too great, as you'll see, so this doesn't work any more.
Another two cryptic symbols are the two that do negation. These only work
with the option `EXTENDED_GLOB
' set: this is necessary to get the most
out of zsh's patterns, but it can be a trap for the unwary by turning
otherwise innocuous characters into patterns:
print ^fooThis means any file in the current directory except the file
foo
.
One way of coming unstuck with `^
' is something like
stty kill ^uwhere you would hope `
^u
' means control with `u
', i.e. ASCII
character 21. But it doesn't, if EXTENDED_GLOB
is set: it means `any
file in the current directory except one called `u
' ', which is
definitely a different thing. The other negation operator isn't usually so
fraught, but it can look confusing:
print *.c~f*is a pattern of two halves; the shell tries to match `
*.c
', but rejects
any matches which also match `f*
'. Luckily, a `~
' right at the
end isn't special, so
rm *.c~removes all files ending in `
.c~
' --- it wouldn't be very nice if it
matched all files ending in `.c
' and treated the final `~
' as an
instruction not to reject any, so it doesn't. The most likely case I can
think of where you might have problems is with Emacs' numeric backup files,
which can have a `~
' in the middle which you should quote. There is no
confusion with the directory use of `~
', however: that only occurs at
the beginning of a word, and this use only occurs in the middle.
The final oddments that don't fit into normal shell globbing are forms with
`#
'. These also require that EXTENDED_GLOB
be set. In the
simplest use, a `#
' after a pattern says `match this zero or more
times'. So `(foo|bar)#.c
' matches foo.c
, bar.c
, foofoo.c
,
barbar.c
, foobarfoo.c
, ... With an extra #
, the pattern before
(or single character, if it has no special meaning) must match at least
once. The other use of `#
' is in a facility called `globbing flags',
which look like `(#X)
' where `X
' is some letter, possibly followed
by digits. These turn on special features from that point in the pattern
and are one of the newest features of zsh patterns; they will receive much
more space in chapter 5.
Redirection means retrieving input from some other file than the usual one,
or sending output to some other file than the usual one. The simplest
examples of these are `<
' and `>
', respectively.
% echo 'This is an announcement' >tempfile % cat <tempfile >newfile % cat newfile This is an announcementHere,
echo
sends its output to the file tempfile
; cat
took its
input from that file and sent its output --- the same as its input --- to
the file newfile
; the second cat
takes its input from newfile
and, since its output wasn't redirected, it appeared on the terminal.
The other basic form of redirection is a pipe, using `|
'. Some people
loosely refer to all redirections as pipes, but that's rather confusing.
The input and output of a pipe are both programmes, unlike the case
above where one end was a file. You've seen lots of examples already:
echo foo | sed 's/foo/bar/'Here,
echo
sends its output to the programme sed
, which substitutes
foo by bar, and sends its own output to standard output. You can chain
together as many pipes as you like; once you've grasped the basic behaviour
of a single pipe, it should be obvious how that works:
echo foo is a word | sed 's/foo/bar/' | sed 's/a word/an unword/'runs another
sed
on the output of the first one. (You can actually
type it like that, by the way; the shell knows a pipe symbol can't be at
the end of a command.) In fact, a single sed
will suffice:
echo foo is a word | sed -e 's/foo/bar/' -e 's/a word/an unword/'has the same effect in this case.
Obviously, all three forms of redirection only work if the programme in question expects input from standard input, and sends output to standard output. You can't do:
echo 'edit me' | vito edit input, since
vi
doesn't use the input sent to it; it always
deals with files. Most simple UNIX commands can be made to deal with
standard input and output, however. This is a big difference from other
operating systems, where getting programmes to talk to each other in an
automated fashion can be a major headache.
The word `clobber', as in the option NO_CLOBBER
which I mentioned in
the previous chapter, may be unfamiliar to people who don't use English as
their first language. Its basic meaning is `hit' or `defeat' or
`destroy', as in `Itchy and Scratchy clobbered each other with mallets'.
If you do:
% echo first go >file % echo second go >filethen
file
will contain only the words `second go'. The first thing you
put into the file, `first go', has been clobbered. Hence the
NO_CLOBBER
option: if this is set, the shell will complain when you try
to overwrite the file. You can use `>|file
' or `>! file
' to
override this. You usually can't use `>!file
' because history
expansion will try to expand `!file
' before the shell parses the line;
hence the form with the vertical bar tends to be more useful.
UNIX-like systems refer to different channels such as input, output and
error by `file descriptors', which are small integers. Usually three are
special: 0, standard input; 1, standard output; and 2, standard error.
Bourne-like shells (but not csh-like shells) allow you to refer to a
particular file descriptor, instead of standard input or output, by putting
the integer immediately before the `<
' or `>
' (no space is
allowed). What's more, if the `<
' or `>
' is followed immediately
by `&
', a file descriptor can follow the redirection (the one
before is optional as usual). A common use is:
% echo This message will go to standard error >&2The command sends its message to standard output, file descriptor 1. As usual, `
>
' redirects standard output. This time, however, it is
redirected not to a file, but to file descriptor 2, which is standard
error. Normally this is the same device as standard output, but it can be
redirected completely separately. So:
% { echo A message cursh> echo An error >&2 } >file An error % cat file A messageApologies for the slightly unclear use of the continuation prompt `
cursh>
': this guide goes into a lot of different formats, and some
are a bit finicky about long lines in preformatted text. As pointed out
above, the `>file
' here will redirect all output from the stuff in
braces, just as if it were a single command. However, the `>&2
'
inside redirects the output of the second echo
to standard
error. Since this wasn't redirected, it goes straight to the terminal.
Note the form in braces in the previous example --- I'm going to use that in a few more examples. It simply sends something to standard output, and something else to standard error; that's its only use. Apart from that, you can treat the bit in braces as a black box --- anything which can produce both sorts of output.
Sometimes you want to redirect both at once. The standard Bourne-like way of doing this is:
% { echo A message cursh> echo An error >&2 } >file 2>&1The `
>file
' redirects standard output from the {
...}
to the
file; the following 2>&1
redirects standard error to wherever standard
output happens to be at that point, which is the same file. This allows
you to copy two file descriptors to the same place. Note that the order is
important; if you swapped the two around, `2>&1
' would copy standard
error to the initial destination of standard output, which is the terminal,
before it got around to redirecting standard output.
Zsh has a shorthand for this borrowed from csh-like shells:
% { echo A message cursh> echo An error >&2 } >&fileis exactly equivalent to the form in the previous paragraph, copying standard output and standard error to the same file. There is obviously a clash of syntax with the descriptor-copying mechanism, but if you don't have files whose names are numbers you won't run into it. Note that csh-like shells don't have the descriptor-copying mechanism: the simple `
>&
' and the same thing with pipes are the only uses of `&
' for
redirections, and it's not possible there to refer to particular file
descriptors.
To copy standard error to a pipe, there are also two forms:
% { echo A message cursh> echo An error >&2 } 2>&1 | sed -e 's/A/I/' I message In error % { echo A message cursh> echo An error >&2 } |& sed -e 's/A/I/' I message In errorIn the first case, note that the pipe is opened before the other redirection, so that `
2>&1
' copies standard error to the pipe, not the
original standard output; you couldn't put that after the pipe in any case,
since it would refer to the `sed
' command's output. The second way is
like csh; unfortunately, `|&
' has a different meaning in ksh (start a
coprocess), so zsh is incompatible with ksh in this respect.
You can also close a file descriptor you don't need: the form `2<&-
'
will close standard error for the command where it appears.
One thing not always appreciated about redirections is that they can occur anywhere on the command line, not just at the end.
% >file echo foo % cat file foo
There are various other forms which use multiple `>
's and `<
's.
First,
% echo foo >file % echo bar >>file % cat file foo barThe `
>
>
' appends to the file instead of overwriting it. Note, however,
that if you use this a lot you may find there are neater ways of doing the
same thing. In this example,
% { echo foo cursh> echo bar } >file % cat file foo barHere, `
cursh>
' is a prompt from the shell that it is waiting for you to
close the `{
' construct which executes a set of commands in the current
shell. This construct can have a redirection applied to the entire
sequence of commands: `>file
' after the closing brace therefore
redirects the output from both echo
s.
In the case of input, doubling the sign has a totally different effect.
The word after the <
<
is not a file, but a string which will be
used to mark in the end of input. Input is read until a line with only
this string is found:
% sed -e 's/foo/bar/' <<HERE heredoc> This line has foo in it. heredoc> There is another foo in this one. heredoc> HERE This line has a bar in it. There is another bar in this one.The shell prompts you with `
heredoc>
' to tell you it is reading a
`here document', which is how this feature is referred to. When it finds
the final string, in this case `HERE
', it passes everything you have
typed as input to the command as if it came from a file. The command in
this case is the stream editor, which has been told to replace the first
`foo
' on each line with a `bar
'. (Replacing things with a bar
is a familiar experience from the city centre of my home town, Newcastle
upon Tyne.)
So far, the features are standard in Bourne-like shells, but zsh has an extension to here documents, sometimes referred to as `here strings'.
% sed -e 's/string/nonsense/' \ > <<<'This string is the entire document.' This nonsense is the entire document.Note that `
>
' on the second line is a continuation prompt, not part
of the command line; it was just too long for the TeX version of this
document if I didn't split it. This is a shorthand form of `here'
document if you just want to pass a single string to standard input.
The final form uses both symbols: `<>file
' opens the file for
reading and writing --- but only on standard input. In other words, a
programme can now both read from and write to standard input. This
isn't used all that often, and when you do use it you should remember
that you need to open standard output explicitly to the same file:
% echo test >/tmp/redirtest % sed 's/e/Z/g' <>/tmp/redirtest 1>&0 % cat /tmp/redirtest tZtstAs standard input (the 0) was opened for writing, you can perform the unusual trick of copying standard output (the 1) into it. This is generally not a particularly safe way of doing in-place editing, however, though it seems to work fine with sed. Note that in older versions of zsh, `
<>
' was equivalent to `<->
', which is a
pattern that matches any number; this was changed quite some time ago.
All Bourne-like shells have two other features. First, the `command'
exec
, which I described above as being used to replace the shell with
the command you give after it, can be used with only redirections after
it. These redirections then apply permanently to the shell itself, rather
than temporarily to a single command. So
exec >filemakes
file
the destination for standard output from that point on. This
is most useful in scripts, where it's quite common to want to change the
destination of all output.
The second feature is that you can use file descriptors which haven't even been opened yet, as long as they are single digits --- in other words, you can use numbers 3 to 9 for your own purposes. This can be combined with the previous feature for some quite clever effects:
exec 3>&1 # 3 refers to stdout exec >file # stdout goes to `file', 3 untouched # random commands output to `file' exec 1>&3 # stdout is now back where it was exec 3>&- # file descriptor 3 closed to tidy upHere, file descriptor 3 has been used simply as a placeholder to remember where standard output was while we temporarily divert it. This is an alternative to the `
{
...} >file
' trick. Note that you can put
more than one redirection on the exec
line: `exec 3>&1 >file
' also
works, as long as you keep the order the same.
Multios allow you to do an implicit `cat
' (concatenate files) on input
and `tee
' (send the same data to different files) on output. They
depend on the option MULTIOS
being set, which it is by default. I
described this in the last chapter in discussing whether or not you should
have the option set, so you can look at the examples there.
Here's one fact I didn't mention. You use output multios like this:
command-generating-output >file1 >file2where the command's output is copied to both files. This is done by a process forked off by the shell: it simply sits waiting for input, then copies it to all the files in its list. There's a problem in all versions of the shell to date (currently 4.0.6): this process is asynchronous, so you can't rely on it having finished when the shell starts executing the next command. In other words, if you look at
file1
or file2
immediately after the command has finished, they may not yet contain all
the output because the forked process hasn't finished writing to it.
This is really a bug, but for the time being you will have to live with it
as it's quite complicated to fix in all cases. Multios are most useful as
a shorthand in interactive use, like so much of zsh; in a script or
function it is safer to use tee
,
command-generating-output | tee file1 file2which does the same thing, but as
tee
is handled as a synchronous
process file1
and file2
are guaranteed to be complete when the
pipeline exits.
I have been rather cavalier in using a couple of elements of syntax without explaining them:
true && print Previous command returned true false || print Previous command returned falseThe relationship between `
&&
' and `||
' and tests is fairly obvious,
but in this case they connect complete commands, not test arguments.
The `&&
' executes the following command if the one before succeeded,
and the `||
' executes the following command if the one before failed.
In other words, the first is equivalent to
if true; then print Previous command returned true fibut is more compact.
There is a perennial argument about whether to use these or not.  On the one hand, the
difference in speed between the two forms is minute, probably measurable
in microseconds rather than milliseconds on a modern computer; the
scheduling of the shell process running the script by the operating
system is likely to make more difference if these are embedded inside a
much longer script or function, as they will be. And on the other hand,
the connection between `&&
' and a logical `and' is so strong in the
minds of many programmers that to anyone with moderate shell experience
they are perfectly readable. So it's up to you. I find I use the
`&&
' and `||
' forms for a pair of simple commands, but use
`if
' for anything more complicated.
I would certainly advise you to avoid chains like:
true || print foo && print bar || falseIf you try that, you will see `
bar
' but not `foo
', which is not
what a C programmer might expect. Using the usual rules of precedence, you
would parse it as: either true
must be true; or both the print
statements must be true; or the false must be true. However, the shell
parses it differently, using these rules:
&&
',
||
' is encountered, or until the group of
commands is ended by a newline, a semicolon, or the end of an
enclosing group. Then execute whatever follows in the normal way.
||
',
&&
' is encountered, or until the end of the group,
and execute what follows normally
||
'.
true
'
succeeds, we skip `print foo
' but execute `print bar
' and then
skip false
. The expression returns status zero because the last
thing it executed did so. Oddly enough, this is completely standard
behaviour for shells. This is a roundabout way of saying `don't use
combined chains of `&&
's and `||
's unless you think Gödel's
theorem is for sissies'.
Strictly speaking, the and's and or's come in a hierarchy of things which
connect commands. They are above pipelines, which explains my remark
above --- an expression like `echo $ZSH_VERSION | sed '/dev//'
' is
treated as a single command between any logical connectors --- and they are
below newlines and semicolons --- an expression like `true && print yes;
false || print no
' is parsed as two distinct sets of logically connected
command sequences. In the manual, a list is a complete set of commands
executed in one go:
echo foo; echo bar echo small furry animals--- a shell function is basically a glorified list with arguments and a name. A sublist is a set of commands up to a newline or semicolon, in other words a complete expression possibly involving the logical connectors:
show -nomoreproc | grep -q foo && print The word '`foo'\' occurs.A pipeline is a chain of one or more commands connected by `
|
', for example both individual parts of the previous sublist,
show -nomoreproc | grep -q fooand
print The word '`foo'\' occurs.count as pipelines. A simple command is one single unit of execution with a command name, so to use the same example that includes all three of the following,
show -nomoreproc grep -q foo print The word '`foo'\' occurs.
This means that in something like
print foowhere the command is terminated by a newline and then executed in one go, the expression is all of the above --- list, sublist, pipeline and simple command. Mostly I won't need to make the formal distinction; it sometimes helps when you need to break down a complicated set of commands. It's a good idea, and usually possible, to write in such a way that it's obvious how the commands break down. It's not too important to know the details, as long as you've got a feel for how the shell finds the next command.
I've shown plenty of examples of one sort of shell structure already, the
if
statement:
if [[ black = white ]]; then print Yellow is no colour. fiThe main points are: the `
if
' itself is followed by some command whose
return status is tested; a `then
' follows as a new command; any number
of commands may follow, as complex as you like; the whole sequence is ended
by a `fi
' as a command on its own. You can write the `then
' on a
new line if you like, I just happen to find it neater to stick it where it
is. If you follow the form here, remember the semicolon before it; the
then
must start a separate command. (You can put another command
immediately after the then
without a newline or semicolon, though,
although people tend not to.)
The double-bracketed test is by far the most common thing to put here in zsh, as in ksh, but any command will do; only the status is important.
if true; then print This always gets executed fi if false; then print This never gets executed fiHere,
true
always returns true (status 0), while false
always
returns false (status 1 in zsh, although some versions return status 255
--- anything nonzero will do). So the statements following the print
s
are correct.
The if
construct can be extended by `elif
' and `else
':
read var if [[ $var = yes ]]; then print Read yes elif [[ $var = no ]]; then print Read no else print Read something else fiThe extension is pretty straightforward. You can have as many `
elif
's
with different tests as you like; the code following the first test to
succeed is executed. If no test succeeded, and there is an `else
'
(there doesn't need to be), the code following that is executed. Note
that the form of the `elif
' is identical to that of `if
',
including the `then
', while the else just appears on its own.
The while
-loop is quite similar to if
. There are two
differences: the syntax uses while
, do
and done
instead of
if
, then
and fi
, and after the loop body is executed (if it is),
the test is evaluated again. The process stops as soon as the test is
false. So
i=0 while (( i++ < 3 )); do print $i doneprints 1, then 2, then 3. As with
if
, the commands in the middle can
be any set of zsh commands, so
i=0 while (( i++ < 3 )); do if (( i & 1 )); then print $i is odd else print $i is even fi donetells you that 1 and 3 are odd while 2 is even. Remember that the indentation is irrelevant; it is purely there to make the structures more easy to understand. You can write the code on a single line by replacing all the newlines with semicolons.
There is also an until
loop, which is identical to the while
loop
except that the loop is executed until the test is true. `until
[[
...' is equivalent to `while ! [[
...'.
Next comes the for
loop. The normal case can best be demonstrated by
another example:
for f in one two three; do print $f donewhich prints out `
one
' on the first iteration, then `two
', then
`three
'. The f
is set to each of the three words in turn, and the
body of the loop executed for each. It is very useful that the words after
the `in
' may be anything you would normally have on a shell command
line. So `for f in *; do
' will execute the body of the loop once for
each file in the current directory, with the file available as $f
, and
you can use arrays or command substitutions or any other kind of
substitution to generate the words to loop over.
The for
loop is so useful that the shell allows a shorthand that you
can use on the command line: try
for f in *; print $fand you will see the files in the current directory printed out, one per line. This form, without the
do
and the done
, involves less
typing, but is also less clear, so it is recommended that you only use it
interactively, not in scripts or functions. You can turn the feature off
with NO_SHORT_LOOPS
.
The case
statement is used to test a pattern against a series of
possibilities until one succeeds. It is really a short way of doing
a series of if
and elif
tests on the same pattern:
read var case $var in (yes) print Read yes ;; (no) print Read no ;; (*) print Read something else ;; esacis identical to the
if
/elif
/else
example above. The $var
is compared against each pattern in turn; if one matches, the code
following that is executed --- then the statement is exited; no further
matches are looked for. Hence the `*
' at the end, which can match
anything, acts like the `else
' of an if
statement.
Note the quirks of the syntax: the pattern to test must appear in
parentheses. For historical reasons, you can miss out the left
parenthesis before the pattern. I haven't done that mainly because
unbalanced parentheses confuse the system I am using for writing this
guide. Also, note the double semicolon: this is the only use of double
semicolons in the shell. That explains the fact that if you type
`;;
' on its own the shell will report a `parse error'; it couldn't
find a case
to associate it with.
You can also use alternative patterns by separating them with a vertical
bar. Zsh allows alternatives with extended globbing anyway; but this is
actually a separate feature, which is present in other shells which don't
have zsh's extended globbing feature; it doesn't depend on the
EXTENDED_GLOB
option:
read var case $var in (yes|true|1) print Reply was affirmative ;; (no|false|0) print Reply was negative ;; (*) print Reply was cobblers ;; esacThe first `
print
' is used if the value of $var
read in was
`yes
', `true
' or `1
', and so on. Each of the separate items
can be a pattern, with any of the special characters allowed by zsh,
this time depending on the setting of the option EXTENDED_GLOB
.
The select
loop is not used all that often, in my experience. It is
only useful with interactive input (though the code may certainly appear in
a script or function):
select var in earth air fire water; do print You selected $var doneThis prints a menu; you must type 1, 2, 3 or 4 to select the corresponding item; then the body of the loop is executed with
$var
set to the value
in the list corresponding to the number. To exit the loop hit the break
key (usually ^G
) or end of file (usually ^D
: the feature is
so infrequently used that currently there is a bug in the shell that this
tells you to use `exit
' to exit, which is nonsense). If the user
entered a bogus value, then the loop is executed with $var
set to the
empty string, though the actual input can be retrieved from $REPLY
.
Note that the prompt printed for the user input is $PROMPT3
, the only
use of this parameter in the shell: all normal prompt substitutions are
available.
There is one final type of loop which is special to zsh, unlike the others
above. This is `repeat
'. It can be used two ways:
% repeat 3 print Hip Hip Hooray Hip Hip Hooray Hip Hip Hooray Hip Hip HoorayHere, the first word after
repeat
is a count, which could be a
variable as normal substitutions are performed. The rest of the line (or
until the first semicolon) is a command to repeat; it is executed
identically each time.
The second form is a fully fledged loop, just like while
:
% repeat 3; do repeat> print Hip Hip Hooray repeat> done Hip Hip Hooray Hip Hip Hooray Hip Hip Hooraywhich has the identical effect to the previous one. The `
repeat>
' is
the shell's prompt to show you that it is parsing the contents of a
`repeat
' loop.
More catching up with stuff you've already seen. The expression in parentheses here:
% (cd ~; ls) <all the files in my home directory> % pwd <where I was before, not necessarily ~>is run in a subshell, as if it were a script. The main difference is that the shell inherits almost everything from the main shell in which you are typing, including options settings, functions and parameters. The most important thing it doesn't inherit is probably information about jobs: if you run
jobs
in a subshell, you will get no output; you can't use
fg
to resume a job in a subshell; you can't use `kill %
n' to
kill a job (though you can still use the process ID); and so on. By now
you should have some feel for the effect of running in a separate process.
Running a command, or set of commands, in a different directory, as in this
example, is one quite common use for this construct. (In zsh 4.1, you
can use jobs
in a subshell; it lists the jobs running in the parent
shell; this is because it is very useful to be able to pipe the output
of jobs into some processing loop.)
On the other hand, the expression in braces here:
% {cd ~; ls} <all the files in my home directory> % pwd /home/pwsis run in the current shell. This is what I was blathering on about in the section on redirection. Indeed, unless you need some special effect like redirecting a whole set of commands, you won't use the current-shell construct. The example here would behave just the same way if the braces were missing.
As you might expect, the syntax of the subshell and current-shell forms is very similar. You can use redirection with both, just as with simple commands, and they can appear in most places where a simple command can appear:
[[ $test = true ]] && { print Hello. print Well, this is exciting. }That would be much clearer using an `
if
', but it works. For some
reason, you often find expressions of this form in system start-up files
located in the directory /etc/rc.d
or, on older systems, in files
whose names begin with `/etc/rc.
'. You can even do:
if { foo=bar; [[ $foo = bar ]] }; then print yes fibut that's also pretty gross.
One use for {
...}
is to make sure a whole set of commands is
executed at once. For example, if you copy a set of commands from a script
in one window and want them to be run in one go in a shell in another
window, you can do:
% { cursh> # now paste your commands in here... ... cursh> }and the commands will only be executed when you hit return after the final `
}
'. This is also a workaround for some systems where cut and paste
has slightly odd effects due to the way different states of the terminal
are handled. The current-shell construct is a little bit like an anonymous
function, although it doesn't have any of the usual features of functions
--- you can't pass it arguments, and variables declared inside aren't local
to that section of code.
In case you're confused about what happens in the current shell and what happens in a subshell, here's a summary.
The following are run in the current shell.
[[
'.
if
and while
.
Tests and code inside the block must both be considered separately.
source
' or `.
' as well as startup files.
{
...}
'.
`
...`
,
$
(...), =
(...),
<
(...) and >
(...).
The following are run in a subshell.
(...)'.
`
...`
, $
(...),
=
(...), <
(...) and
>
(...). (TCL fans note that this is different
from the `[
...]
' command substitution in that language.)
&
' at the end.
^Z
. Since the shell needs to return you to
the prompt, it forks a subshell to remember the commands it was
executing when you interrupted it. If you use fg
or bg
to
restart, the commands will stay in the subshell. This is a special
feature of zsh; most shells won't let you interrupt anything in the
current shell like that, though you can still abort it with ^C
.
Of course, if for some reason the current set of commands is already running in a subshell, it doesn't get magically returned to the current shell --- so a shell builtin on the left hand side of a pipeline is running in a subshell. However, it doesn't get an extra subshell, as an external command would. What I mean is:
{ print Hello; cat file } | while read line; print $line; doneThe shell forks, producing a subshell, to execute the left hand side of the pipeline, and that subshell forks to execute the
cat
external command,
but nothing else in that set of commands will cause a new subshell to be
created.
(For the curious only: actually, that's not quite true, and I already
pointed this out when I talked about command substitutions: the shell keeps
track of occasions when it is in a subshell and has no more commands to
execute. In this case it will not bother forking to create a new process
for the cat
, it will simply replace the subshell which is not needed
any more. This can only happen in simple cases where the shell has no
clearing up to do.)
I described the options you need to set for compatibility with ksh in the previous chapter. Here I'm more interested in the best way of running ksh scripts and functions.
First, you should remember that because of all zsh's options you can't
assume that a piece of zsh code will simply run a piece of sh or ksh code
without any extra changes. Our old friend SH_WORD_SPLIT
is the most
common problem, but there are plenty of others. In addition to options,
there are other differences which simply need to be worked around. I
will list some of them a bit later. Generally speaking, Bourne shell is
simple enough that zsh emulates it pretty well --- although beware in case
you are using bash extensions, since to many Linux users bash is the
nearest approximation to the Bourne shell they ever come across. Zsh makes
no attempt to emulate bash, even though some of bash's features have been
incorporated.
To make zsh emulate ksh or sh as closely as it knows how, there are various things you can do.
ARGV0=sh zsh
' runs zsh under the name sh, just like the symbolic
link method.
emulate ksh
' at the top of the script or function you
want to run. In the case of a function, it is better to run
`emulate -L ksh
' since this makes sure the normal options will
be restored when the function exits; this is irrelevant for a script
as the options cannot be propagated to the process which ran the
script. You can also use the option `-R
' after emulate
, which
forces more options to be like ksh; these extra options are generally
for user convenience and not relevant to basic syntax, but in some
cases you may want the extra cover provided.
If it's possible the script may already be running under ksh, you can instead use
[[ -z $ZSH_VERSION ]] && emulate kshor for sh, using the simpler test command there,
[ x$ZSH_VERSION = x ] && emulate sh
Emulation of ksh88 is much better than emulation of ksh93. Support for the latter is gradually being added, but only patchily.
There is no easy way of converting code written for any csh-like shell; you will just have to convert it by hand. See the FAQ for some hints on converting aliases to functions.
Here are some differences from ksh88 which might prove significant for ksh programmers. This is lifted straight from the corresponding section of the FAQ; it is not complete, and indeed some of the `differences' could be interpreted as bugs. Those marked `*' perform in a ksh-like manner if the shell is invoked with the name `ksh', or if `emulate ksh' is in effect.
array[0]
refers to array[1]
;
$array
refers to the whole array, not $array[0]
;
braces are unnecessary: $a[1] == ${a[1]}
, etc.
The KSH_ARRAYS
option is now available.
coproc
; |&
behaves like
csh. Handling of coprocess file descriptors is also different.
cmd1 && cmd2 &
, only cmd2
instead of the whole
expression is run in the background in zsh. The manual implies
this is a bug. Use { cmd1 && cmd2 } &
as a workaround.NO_NOMATCH
).
foo="*"; print $foo
prints all files in ksh but *
in zsh
(unset GLOB_SUBST
).
$PSn
do not do parameter substitution by default (use PROMPT_SUBST
).
^
, ~
and #
(but not |
) forms require EXTENDED_GLOB
.
From version 3.1.3, the ksh forms are fully supported when the
option KSH_GLOB
is in effect.
[1] Note that ~
is the only globbing operator to have a lower
precedence than /
. For example, **/foo~*bar*
matches any
file in a subdirectory called foo
, except where bar
occurred somewhere in the path (e.g. users/barstaff/foo
will
be excluded by the ~
operator). As the **
operator cannot
be grouped (inside parentheses it is treated as *
), this is
the way to exclude some subdirectories from matching a **
.
integer
does not allow -i
.
typeset
and integer
have special behaviour for
assignments in ksh, but not in zsh. For example, this doesn't
work in zsh:
integer k=$(wc -l ~/.zshrc)because the return value from
wc
includes leading
whitespace which causes wordsplitting. Ksh handles the
assignment specially as a single word.$ENV
variable (use /etc/zshrc
, ~/.zshrc
;
note also $ZDOTDIR
).
$PATH
is not searched for commands specified
at invocation without -c.emacs
, gmacs
, viraw
are not supported.
Use bindkey to change the editing behaviour: set -o {emacs,vi}
becomes bindkey -{e,v}
; for gmacs, go to emacs mode and use
bindkey \^t gosmacs-transpose-characters
.
keyword
option does not exist and -k
is instead
interactivecomments. (keyword
will not be in the next ksh
release either.)
SHARE_HISTORY
appeared in 3.1.6 and is set in ksh
compatibility mode to remedy this.
\
does not escape editing chars (use ^V
).
<ESC>#
; try <ESC>q
).
#
in an interactive shell is not treated as a comment by
default.
r
, autoload
, history
, integer
...)
were aliases in ksh.
alias
newgrp="exec newgrp"
jobs
has no -n
flag.
read
has no -s
flag.
select
always redisplays the list of selections on each loop.
There are also problems in making your own scripts and functions available to other people, who may have different options set.
In the case of functions, it is always best to put `emulate -L zsh
'
at the top of the function, which will reset the options to the default
zsh values, and then set any other necessary options. It doesn't take
the shell a great deal of time to process these commands, so try and
get into the habit of putting them any function you think may be used by
other people. (Completion functions are a special case as the
environment is already standardised --- see chapter 6 for this.)
The same applies to scripts, since if you run the script without using
the option `-f
' to zsh the user's non-interactive startup files will
be run, and in any case the file /etc/zshenv
will be run. We urge
system administrators not to set options unconditionally in that file
unless absolutely necessary; but they don't always listen. Hence an
emulate
can still save a lot of grief.
Here are some final comments on running scripts: they apply regardless of the problems of portability, but you should certainly also be aware of what I was saying in the previous section.
You may be aware that you can force the operating system to run a script
using a particular interpreter by putting `#!
' and the path to the
interpreter at the top of the script. For example, a zsh script could
start with
#!/usr/local/bin/zsh print The arguments are $*assuming that zsh lives in the directory
/usr/local/bin
. Then you
can run the script under its name as if it were an ordinary command.
Suppose the script were called `scriptfile
' and in the current
directory, and you want to run it with the arguments `one two
forty-three
'. First you must make sure the script is executable:
% chmod +x scriptfileand then you can run it with the arguments:
% ./scriptfile one two forty-three The arguments are one two forty-threeThe shell treats the first line as a comment, since it begins with a `
#
', but note it still gets evaluated by the shell; the system
simply looks inside the file to see if what's there, it doesn't change
it just because the first line tells it to execute the shell.
I put the `./
' in front to refer to the current directory because I
don't usually have that in my path --- this is for safety, to avoid
running things which happen to have names like commands simply because
they were in the current directory. But many people aren't so paranoid,
and if `.
' is in your path, you can omit the `./
'. Hence,
obviously, it can be anywhere else in your path: it is searched for as
an ordinary executable.
The shell actually provides this mechanism even on operating systems
(now few and far between in the UNIX world) that don't have the feature
built into them. The way this works is that if the shell found the
file, and it was executable, but running it didn't work, then it will
look for the #!
, extract the name following and run (in this
example) `/usr/local/bin/zsh
<path>/scriptfile one two
forty-three
', where <path> is the path where the file was found.
This is, in fact, pretty much what the system does if it handles it
itself.
Some shells search for scripts using the path when they are given as
filenames at invocation, but zsh happens not to. In other words,
`zsh scriptfile
' only runs scriptfile
in the current directory.
There are two other features you may want to be aware of. Both are down
to the operating system, if that is what is responsible for the `#!
'
trick (true of all the most common UNIX-like systems at the moment).
First, you are usually allowed to supply one, but only one, argument or
option in the `#!
' line, thus:
#!/usr/local/bin/zsh -f print other stuff herewhich stops startup files other than
/etc/zshenv
from being run, but
otherwise works the same as before. If you need more options, you
should combine them in the same word. However, it's usually clearer,
for anything apart from -f
, -i
(which forces the shell into
interactive mode) and a few other options which need to take effect
immediately, to put a `setopt
' line at the start of the body of the
script. In a few versions of zsh, there was an unexpected consequence
of the fact that the line would only be split once: if you accidentally
left some spaces at the end of the line (e.g. `#!/usr/local/bin/zsh
-f
') they would be passed down to the shell, which would report an
error, which was hard to interpret. The spaces will still usually be
passed down, but the shell is now smart enough to ignore spaces in an
option list.
The second point is that the length of the `#!
' line which will be
evaluated is limited. Often the limit is 32 characters, in total, That
means if your path to zsh is long, e.g.
`/home/users/psychology/research/dreams/freud/solaris_2.5/bin/zsh
'
the system won't be able to find the shell. Your only recourse is to
find a shorter path, or execute the shell directly, or some sneakier
trick such as running the script under /bin/sh
and making that start
zsh when it detects that zsh isn't running yet. That's a fairly nasty
way of doing it, but just in case you find it necessary, here's an
example:
#!/bin/sh if [ x$ZSH_VERSION = x ]; then # Put the right path in here --- # or just rely on finding zsh in # $path, since `exec' handles that. exec /usr/local/bin/zsh $0 "$@" fi print $ZSH_VERSION print Hello, this is $0 print with arguments $*.Note that first `
$0
', which passes down the name of the script that was
originally executed. Running this as `testexec foo bar
' gives me
3.1.9-dev-8 Hello, this is /home/pws/tmp/testexec with arguments foo bar.I hope you won't have to resort to that. By the way, really, excruciatingly old versions of zsh didn't have
$ZSH_VERSION
. Rather
than fix the script, I suggest you upgrade the shell. Also, on some old
Bourne shells you might need to replace "$@"
with ${1+"$@"}
,
which is more careful about only putting in arguments if there were any
(this is the sort of thing we'll see in chapter 5). Usually
this isn't necessary.
You can use the same trick on ancient versions of UNIX which didn't
handle `#!
'. On some such systems, anything with a `:
' as the
first character is run with the Bourne shell, so this serves as an
alternative to `#!/bin/sh
', while on some Berkeley systems, a plain
`#
' caused csh to be used. In the second case, you will need to
change the syntax of the first test to be understood by both zsh and
csh. I'll leave that as an exercise for the reader. If you have perl
(very probable these days) you can look at the perlrun
manual page,
which discusses the corresponding problem of starting perl scripts from
a shell, for some ideas.
There's one other glitch you may come across. Sometimes if you type the
name of a script which you know is in your path and is executable, the
shell may tell you `file not found
', or some equivalent message.
What this usually means is that the interpreter wasn't found,
because you mistyped the line after the `#!
'. This confusing
message isn't the shell's fault: a lot of operating systems return the
same system error in this case as if the script were really not found.
It's not worth the shell searching the path to see if the script is
there, because in the vast majority of cases the error refers to the
programme in the execution path. If the operating system returned the
more natural error, `exec format error
', then the shell would know
that there was something wrong with the file, and could investigate; but
unfortunately life's not that simple.