New Shells, Part One: Zoidberg

bash, tcsh, zsh, and their kin are all subspecies of the same beast. This month, let’s look at the first of a number of different breeds: a Perl shell, Zoidberg.

If you’re like most intermediate-to-advanced Linux users, you’ve no doubt run into limitations of your shell and, to work around those limits, you’ve resorted to writing a script in an interpreted language like Python or Perl. Or perhaps you just “think Perl” — your fingers type Perl code without a second of thought. Either way, knowing about alternatives to traditional shells can save you work. After all, to start a new shell, all you have to do is type its name at any command-line.

This month, let’s look at the first of several nontraditional shells: Zoidberg, or zoid, a shell written in Perl. (There are several Perl shells, by the way, including Psh from Gregor Purdy and others.) This article is based on the Debian zoidberg package, version 0.93, which is slightly behind the current zoid version (at this writing) of 0.95. Jaap Karssenberg is Zoidberg’s founder and main developer. (The Zoidberg shell is still in beta, so you may not want to use it as your login shell.)

To get the most from zoid and this article you need to know a little Perl. And there isn’t room in three pages for more than a brief introduction to zoid. To find out more, see http://zoidberg.student.utwente.nl/. A detailed look at extending Zoidberg can be found in “Zoidberg: A Shell that Speaks Perl” in the April 2005 issue of The Perl Journal.

The Lowdown

Common Linux shells like bash and tcsh are fairly similar overall: print a prompt and read a command-line (or read a script file), handle pipes and redirection, iterate through loops, store shell and environment variables, and run utilities like cp and grep.

A Perl shell could be as simple as this example from Programming Perl, Second Edition:

while (<>) { eval; print $@; }

Although that loop runs Perl code you type, it doesn’t (easily) do some things you expect from a command-line: printing a prompt, letting you edit your input, or controlling processes (job control). And, of course, typing system over and over to run Linux utilities is a pain.

zoid combines basic Bourne shell features with the ability to type Perl code anytime you need to do something that a Bourne shell can’t. Even if you aren’t a Perl hacker, knowing that there’s “More Than One Way To Do It” lets you pick up a Perl book and find an answer when basic shell and utility commands can’t do the job.

Perl Commands vs. POSIX Shell Commands

zoid handles both POSIX- type shell syntax, which zoid calls “command syntax,” and Perl syntax. If it recognizes a command as Perl, zoid uses Perl syntax; otherwise it uses command syntax. (You can also mix the two, which you’ll see shortly.) It’s important to understand which syntax is which. This dual-syntax ability is a lot of the power of zoid, but it can also be something of a nuisance as you get started, when the shell does something you don’t expect.

One caveat: zoid doesn’t implement every construct of POSIX-type shells. For instance, you have to write loops using Perl syntax. File redirection (<, >) and pipes (|) work as in standard shells, as does job control (bg, fg, Control-Z, and so on) — generally, at least. The section “Job List Control” points out some differences.

The most straightforward way to force Perl syntax is to enclose a command in curly braces, as shown in Listing One. The listing starts with a change-directory command (which is part of zoid) and an ls (which runs the Linux ls utility). Using { runs the Perl unlink() function to remove the file a. In the next line, though, which doesn’t use curly braces, zoid runs the Linux utility /usr/bin/unlink instead. (If you don’t like this behavior, you can configure zoid to use the Perl unlink() by default.)

You can also see that, like a Perl program, zoid doesn’t complain when a Perl function fails. Perl’s unlink can’t remove the non-existent file d, but you won’t know that unless you ask zoid to tell you, as is shown in the last unlink() command. A final ls shows that the file a has indeed been removed.

Listing One: Perl commands vs. POSIX commands

$ cd zoidtest
$ ls
a b c
$ { unlink “a” }
$ unlink “d”
unlink: cannot unlink `d': No such file or directory
$ { unlink “d” }
$ { unlink “d” or warn }
Warning: something's wrong at (eval 42) line 3.
$ ls
b c

Another way to use Perl syntax is to start a command with a zoid reserved word such as print and for. You can see the reserved words by listing the hash named settings; look for the array keywords:

$ output(->{settings})

The routine output is built-in and outputs data structures nicely; it calls Data::Dumper for complex data. The previous command actually emits the value of $self, also known as $shell, which contains the main shell object. It’s used so often that if you use the dereference operator -> without an object, zoid acts on $shell.

A couple of other ways to get Perl syntax are by starting a line with a subroutine call or a Perl variable (or a dereference operator). Here’s a verbose way of changing the current directory:

->{commands}{cd}->('zoidtest')

Notice that you don’t need semicolons (;) with single commands.

You can run a Linux utility by typing its name and a set of arguments at a prompt, just as you would with a shell like bash. But you can also type them like Perl functions. If you use a non-existent Perl function, zoid assumes that it’s a Linux utility. In Perl scalar or list context, the command returns its output; otherwise the command prints to stdout (by default, your screen).

So, here’s one way to use scp to copy the file named fl to each remote host listed, one per line, in the file hosts:

$ scp('fl', $_ . ':.') for cat('hosts')

That uses the Perl for (foreach) modifier to build a series of commands like scp fl hostname:.. The Perl dot (.) operator joins each hostname (from $_) with a literal colon and dot (:.), telling scp to write the file to the remote home directory.

Variables

Of course, zoid lets you use Perl arrays, variables, hashes, and so on. If you use the name of a Linux environment variable in Perl context, zoid uses that variable.

For example, to copy PATH and replace the colon delimiters with spaces, type:

$ { ($pathspace = $PATH) =~ s/:/ /g }
$ print $pathspace
/usr/local/bin /usr/bin /bin /usr/bin/X11 .

You can also use environment variables as Perl arrays. To add (for instance) /home/jpeek/bin to the end of PATH:

$ push @PATH, “$HOME/bin”

Filename Globbing

Zoid supports POSIX-type shell globbing (wildcards). For instance, to list all two-character filenames:

$ ls -l ??

But zoid also has Perl-type regular expression (regex) globbing. For example, here are three ways to copy all files whose names contain only digits:

$ cp m{^[0-9]+$} backups
$ cp m{^\d+$} backups
$ cp m{^\p{IsDigit}+$} backups

Note that regex globbing gives pathnames like ./123 instead of plain filenames like 123.

Unless it’s anchored with ^ or $, a regex matches characters anywhere in a name. So, to match a name containing a digit, the simple m{\d} suffices. This is handy for the second style of regex globbing.

A slash (/) in the glob pattern makes a pathname separator (actually, a list of regexs with the slash as the list separator). This lets you write patterns like the next one, which does case-insensitive matching to find directory names that contain the string old that also contain files who names end with .txt:

$ ls -l m{old/\.txt$}i

As in Perl scripts, the i modifier makes the regular expression case-insensitive.

Tab Completion

Several Linux shells offer TAB completion. (Two are bash and zsh.) TAB completion lets you type part of a filename, pathname, option, or other parts of a command-line followed by the TAB key to see all of the possibilities. (Your shell’s TAB completion feature may not be enabled by default.)

Zoid has TAB completion, too. Listing Two shows a few examples. In the first example, type a command and a dash (-) and then press TAB; zoid lists all of the cat options. You can then type the rest of the option (say, s) and a space to start the next argument. (If you need more information about each option, type cat --help and press ENTER to run the command-line.)

TAB completion is context-sensitive. Continuing in Listing Two, example: Now that the command line has cat -s, you can type the start of a filename or pathname and press TAB to see the files and directories files that are available. On the second command-line, pressing TAB shows everything in /usr/.

What makes zoid TAB completion interesting is that it can complete Perl. For instance, the third command in Listing Two shows the possible something s in $->{commands}{ something}.

Tab completion doesn’t work everywhere in every case, but it’s handy when it does.

Listing Two: Examples of TAB completion in zoid

$ cat -TAB
--help --show-all -e -t
--number --squeeze-blank -n -u
--number-nonblank -A -r -vE
--reversible -b -s -vT

$ cat -s /usr/TAB
X11R6/ doc/ info/ lost+found/
bin/ games/ lib/ sbin/
dict/ include/ local/ share/
.

$ ->{commands}TAB
CLEAR {SetHistory} {newgrp}
DELETE {alias} {plug}
EXISTS {bg} {popd}
FETCH {builtin} {pushd}
FIRSTKEY {cd} {pwd}
NEXTKEY {command} {readline}

Job List Control

As you dig into the dark corners of shells, you’ll find subtle differences in features that are otherwise the same. For instance, the jobs command in one shell tells which signal stopped a job, but another shell says only Stopped(signal).

One fairly new feature in zoid is job list control. Other shells let you suspend only one command in a job, but zoid can suspend every command in a job list. This is easiest to explain by example, by comparing zoid to other shells.

Let’s say you’re running a command-line with more than one command — the commands are joined by &&, || or ;. Here are two examples:

$ scp remhost:file . && vi file

and:

$ sleep 10; ls

What happens if you type Control-Z while the first command is running? Different shells do different things: bash suspends the first command and immediately runs the second command. tcsh and zsh seem to suspend the first command and never execute the second command, even after the first command eventually finishes. (Those may not apply to all versions of those shells.) zoid, on the other hand, suspends the first command and waits to execute the second command until the first command is restarted and has finished.

What’s more, zoid lets you add another command to a suspended list. Here’s how:

$ scp remhost:file . && vi file
^Z
[1]+ Stopped scp remhost:file .
$ && scp file remhost:.
$ fg
[1]+ Running scp remhost:file .
. later, I starts.
. then I runs.

After that last fg, the first scp finishes. Then (assuming the first scp returns a zero status — which means “success”) vi starts. When you exit vi (again, with a zero status), zoid runs the second scp to copy the edited file back to the remote host.

Finding Out More

There’s more to zoid, but as often the case for a new program in beta, some of it isn’t at all well-documented or even described. You can check the Zoidberg home page for a handy “Quick Start” introduction, a wiki, manual pages, and more. You’ll find a bit of discussion in the zoidberg-devel mailing list at http:// sourceforge.net/mailarchive/forum.php?forum_id=34647. But reading the shell’s Perl code may be the best way to spot features. If you’re a Perl Hacker, chances are that you’ll decide that Zoidberg is well worth the effort.

Jerry Peek is a freelance writer and instructor who has used Unix and Linux for over 20 years. He’s happy to hear from readers; see https://www.jpeek.com/contact.html.

[Read previous article] [Read next article]
[Read Jerry’s other Linux Magazine articles]