Summary: 45-minute talk highlighting some new and unique features of two shells for UNIX-like systems: zsh and bash version 2.
Note: the talk was written on a short deadline, so the notes are rough. Sorry; I hope this is more useful than no notes at all...
COOL FEATURES OF THE zsh AND bash2 SHELLS
Jerry Peek
20 July 2000
OVERVIEW: 30-minute talk highlighting some new and unique features of
two shells for UNIX-like systems: zsh and bash version 2.
PRESENTER: Jerry Peek, main author of "UNIX Power Tools":
jpeek@jpeek.com, https://www.jpeek.com/. I'm writing a third edition
of the book and studying these two shells (among others) as I write...
----------------------------------------------------------------------
----------------------------------------------------------------------
BACKGROUND:
I assume you've used a UNIX-like system and a common shell. Quick review:
What's a shell? It's the command-line user interface to UNIX systems:
- prints a prompt string
- waits for user to type command line (allows editing)
- parses command line
- starts/manages process(es)
- repeats cycle...
UNIX systems have many shells.
- First two major shells: Bourne, C
- Others: Korn, bash, tcsh, zsh, etc.
- bash (Bourne-again Shell) mainly superset of Bourne shell with some
C shell features. Version 2, out recently, has even more features.
- zsh (zed shell) a hybrid of Bourne, Korn and C shell.
Can emulate other shells, adds many unique features.
----------------------------------------------------------------------
----------------------------------------------------------------------
BASH VERSION 2
----------------------------------------------------------------------
OVERVIEW:
- Probably(?) most-used shell, at least under Linux
- bash version 1 out for years; version 2 released ~2 years ago,
now at 2.03
- FAQ: ftp://ftp.cwru.edu/pub/bash/FAQ has much more info
----------------------------------------------------------------------
SOME OF MY FAVOURITE NEW FEATURES OVER VERSION 1:
- one-dimensional arrays
- new expansions: substring extraction, pattern replacement,
indirect variable expansion
- new variables, including DIRSTACK, PIPESTATUS and GLOBIGNORE
- new tilde prefixes that expand to directories from the directory stack
- builtin commands updated/expanded
- support for POSIX international characters (character classes,
equivalence classes, collating symbols)
- egrep-style extended pattern matching operators
- case-insensitive globbing (filename expansion): "shopt -s nocaseglob"
- menu completion
- many more
----------------------------------------------------------------------
ARRAYS:
- zero-based
- Setting:
name=(value0 value1 value2 ...)
name[n]=valuen
read -a name
- Expanding:
${name[value]} (curly-braces required)
----------------------------------------------------------------------
NEW EXPANSIONS:
- SUBSTRING EXPANSION with ${param:offset} and ${param:offset:length}
Expands to up to "length" (if any) characters of "parameter", starting
at "offset". "length" and "offset" are arithmetic expressions.
Examples:
$ echo $word
supercalafragilisticexpialidocious
$ echo ${word:20}
expialidocious
$ echo ${word:20:7}
expiali
$ echo ${word:20:10-3}
expiali
- PATTERN REPLACEMENT: Expand a parameter, replacing a (glob-type)
pattern with a string. Replace either one match or all matches.
This can do much more than these brief examples show:
$ echo $adage
Few women admit their age; Fewer men act it.
$ echo ${adage/Few/Some}
Some women admit their age; Fewer men act it.
$ echo ${adage//Few/Dumb}
Dumb women admit their age; Dumber men act it.
$ echo ${adage//[ ;]/-}
Few-women-admit-their-age--Fewer-men-act-it.
$ echo ${adage/;*/ and that\'s the truth}
Few women admit their age and that's the truth
- INDIRECT VARIABLE EXPANSION: If first character of parameter name is
exclamation point (!), adds a level of indirection. The value of the
variable formed from the first expansion becomes the name of the
variable which is then expanded again. An example helps:
$ leader=joe
$ follower=ann
$ which=leader
$ echo ${which}
leader
$ echo ${!which}
joe
----------------------------------------------------------------------
NEW VARIABLES:
- DIRSTACK: Array containing directory stack. Example:
$ dirs
/foo/bar/baz/haha/hoho/rubbish /foo/bar/baz/backups
$ pushd
$ cp ${DIRSTACK[1]}/whatever .
$ cp ~1/whatever-else . (shorter version)
$ pushd
- GLOBIGNORE: Colon-separated list of filenames NOT to match in wildcard
expansion (globbing).
NOTE: Automatically enables dotglob (wildcards will match filenames
starting with a dot).
Example:
$ GLOBIGNORE='*~:.*'
$ ls
foo foo~
$ ls -a
.hmm .mmm .omm foo foo~
$ echo *
foo
----------------------------------------------------------------------
EXTENDED egrep-STYLE PATTERN MATCHING: If extglob option is set (using
shopt builtin command), egrep-style GLOB (NOT regular expression)
matching is allowed in pathname expansion. Allows alternation
(multiple matching patterns). The five operations are:
?(pattern-list) Zero or one occurrence of the given pattern(s)
*(pattern-list) Zero or more occurrences of the given pattern(s)
+(pattern-list) One or more occurrences of the given pattern(s)
@(pattern-list) Exactly one of the given pattern(s)
!(pattern-list) Anything except one of the given pattern(s)
Examples:
$ alias l='ls -d'
$ l p*
pam.d passwd- pine.conf printcap profile.d
paper.config passwd.OLD pine.conf.fixed printcap.bak protocols
passwd pcmcia ppp profile pwdb.conf
$ l p@(ine|rint)*
bash2: syntax error near unexpected token `p@(i'
$ shopt -s extglob
$ l p@(ine|rint)*
pine.conf pine.conf.fixed printcap printcap.bak
$ l p!(*conf*|pp)
pam.d passwd- pcmcia printcap.bak profile.d
passwd passwd.OLD printcap profile protocols
pine.conf ppp printcap.bak profile.d pwdb.conf
pine.conf.fixed printcap profile protocols
$ l p!([ac])*
pam.d passwd- pine.conf printcap profile.d
paper.config passwd.OLD pine.conf.fixed printcap.bak protocols
passwd pcmcia ppp profile pwdb.conf
----------------------------------------------------------------------
MENU COMPLETION: Instead of listing all possible completions when you
press TAB twice, shows the next possible completion each time you
press TAB. Off by default; enable it in readline setup file.
Example:
$ cat $INPUTRC
TAB: menu-complete
$ ch<TAB>
$ chgrp <TAB>
$ chmod <TAB>
$ chown jpeek ~/.ba<TAB>
$ chown jpeek ~/.bash_history <TAB>
$ chown jpeek ~/.bash_logout <TAB>
$ chown jpeek ~/.bash_profile <ENTER>
----------------------------------------------------------------------
AGAIN: THERE ARE MANY OTHER CHANGES IN bash2 THAT I HAVEN'T SHOWN.
----------------------------------------------------------------------
----------------------------------------------------------------------
zsh
OVERVIEW:
- Very flexible shell, under active development: "feeping creatureism?"
- FAQ: https://zsh.sourceforge.io/FAQ/
- I won't try to cover all of it... I'll just give you some flavour.
(In fact, I'm not sure *anyone* knows all of this stuff! ;-)
----------------------------------------------------------------------
PROGRAMMABLE COMPLETION:
- Completion of a command's arguments may be different for each command
(the compctl command). You also can specify the way that the command
itself is completed (see compctl -C).
- New, more elegant completion system in version 3.1.6 not covered here.
- Example: the "mail" command has three default substring completions:
zsh% compctl -s 'tim@foo.com ann@bar.com jpeek@jpeek.com' mail
zsh% mail w<TAB>
zsh% mail jpeek@jpeek.com t<TAB>
zsh% mail jpeek@jpeek.com tim@foo.com
- Example: "fg" command completes with with the first word
of a job's command line (compctl -j). When a word completes after
"fg", add a "%" string before it (compctl -P "string"):
zsh% compctl -j -P "%" fg
zsh% jobs
[1] - suspended man zshall
[2] + suspended vi ukuug-talk
zsh% fg m<TAB>
zsh% fg %man
- Many, many, many more. See zshcompctl(1) manpage for details.
----------------------------------------------------------------------
COMMAND LINE EDITING:
- Multiline editing -- without invoking external editor. Example --
enter a for loop, get an error, use vi "up" key to recall entire loop:
zsh% bindkey -v (vi-mode editing)
zsh% for f in *
for> do expand $f | pr -h $f
for> done | lpz
zsh: command not found: lpz
zsh% <ESC><k>for f in *
do expand $f | pr -h $f
done | lpz<CURSOR>
- See zshzle(1) manpage for details
----------------------------------------------------------------------
GLOBBING (FILENAME GENERATION):
- Recursive globbing, like find(1): the pattern
**/bar
searches recursively, matching all pathnames ending with "bar"
(you can also name specific starting directories). So:
zsh% cd /etc
zsh% ls -l **/*sendmail*
-rwxr-xr-x 1 root 1549 Sep 1 1999 rc.d/init.d/sendmail
lrwxrwxrwx 1 root 18 Mar 8 12:28 rc.d/rc0.d/K30sendmail
-> ../init.d/sendmail
lrwxrwxrwx 1 root 18 Mar 8 12:28 rc.d/rc1.d/K30sendmail
-> ../init.d/sendmail
-rw-r--r-- 1 root 34175 Sep 1 1999 sendmail.cf
-rwxr-xr-x 1 root 20 Sep 1 1999 sysconfig/sendmail
- Attribute qualifiers: Wildcard patterns may end in a list of
qualifiers in parentheses. A few qualifiers:
/ directories
. plain files
@ symbolic links
* executable plain files (0100)
r owner-readable files (0400)
w owner-writable files (0200)
x owner-executable files (0100)
A group-readable files (0040)
E group-executable files (0010)
X world-executable files (0001)
...and so on...
So, to list all symbolic links in the current directory:
zsh% ls -l *(@)
lrwxrwxrwx 1 root 11 Mar 8 12:12 rmt -> ../sbin/rmt
The list matches if at least one qualifier matches (they're "OR'ed").
So, to remove execute permission from all world-executable files:
zsh% chmod o-x *(.X)
----------------------------------------------------------------------
MULTIPLE I/O REDIRECTION:
- The MULTIOS option allows multiple input and output redirections:
zsh% setopt MULTIOS
- Multiple input redirections are handled in order, left-to-right.
So this:
zsh% cat file1 file2 | mail -s 'all results' joe@foo.ac.uk
can be written as follows in zsh:
zsh% mail -s 'all results' joe@foo.ac.uk <file1 <file2
- Multiple output redirections are all done at once, like tee(1):
zsh% echo Hi mum >a >b
zsh% cat a
Hi mum
zsh% cat b
Hi mum
This works with filename wildcards too.
So, to append an "exit 0" to all shell scripts (names ending in .sh):
zsh% echo exit 0 >> *.sh
----------------------------------------------------------------------
PATH EXPANSION:
- As in other shells, ~ and ~user are recognised.
If ~dirname matches no usernames, it's looked up as a named directory
(a shell variable whose value starts with "/"). Example:
zsh% work=/etc/rc.d/rc0.d
zsh% cd ~work
zsh% pwd
/etc/rc.d/rc0.d
- Command name prefixed with "=" is substituted with its full pathname:
zsh% ls -l =perl
-rwxr-xr-x 2 root 517052 Aug 31 1999 /usr/bin/perl
----------------------------------------------------------------------
SHELL EMULATION:
- In general, zsh understands both sh- and csh-type operations.
For instance, it has both kinds of loops:
zsh% for f in *.cc
for> do g++ $f
for> done
zsh% foreach f (*.cc)
foreach> g++ $f
foreach> end
- Parameters like PATH, path, etc. supported in both sh and csh syntaxes:
zsh% echo $PATH
/bin:/usr/bin:/usr/X11R6/bin:/home/thomas/bin:/usr/local/bin:
zsh% echo $path
/bin /usr/bin /usr/X11R6/bin /home/thomas/bin /usr/local/bin .
- To totally emulate other shells (csh not emulated fully):
zsh% emulate sh
...acts like Bourne shell...
zsh% emulate ksh
...acts like Korn shell...
...etc...
----------------------------------------------------------------------
VARIABLE EDITING:
- Builtin "vared" command loads parameter value into internal editor
buffer, then re-sets value after editor completes.
- Lots of clever uses. Simple example -- modify PATH as you work:
zsh% vared PATH
/bin:/usr/bin:/usr/X11R6/bin:/home/thomas/bin:/usr/local/bin:
----------------------------------------------------------------------
ARRAY OPERATIONS:
- Many
----------------------------------------------------------------------
CAUTIONS: zsh handles command line evaluation (including word splitting)
differently than other shells unless you set compatibility options.
- Word splitting: values of variables aren't split at spaces as in
other shells. (Use an array instead!)
bash$ files='passwd passwd- passwd.OLD'
bash$ ls -l $files
-rw-r--r-- 1 root root 702 Jul 18 13:43 passwd
-rw-r--r-- 1 root root 702 Jul 18 13:41 passwd-
-rw-r--r-- 1 root root 696 Mar 8 12:32 passwd.OLD
zsh% files='passwd passwd- passwd.OLD'
zsh% ls $files
ls: passwd passwd- passwd.OLD: No such file or directory
- Order of evaluation: Complex command lines that work in other shells
may fail in zsh.
Contact Jerry Peek