New Shells, Part Three: Fish

The Friendly Interactive Shell, or fish, is “friendly” because it’s easier to use, not because it’s “dumbed down.”

The past two columns introduced two fairly new and nontraditional shells: zoidberg and vshnu. The former is written in Perl and accepts Perl code on the command-line, while the latter is is very visual, allowing you to navigate and manipulate the file system with one or two keystrokes at a time. Both avoid the hassle of learning yet another syntax.

Indeed, if you’ve felt frustrated by the syntax of a traditional shell, you’re in the same boat as the author of the Friendly Interactive Shell, or fish, used to be — until he decided to write fish, that is. Axel Liljencrantz explains his beefs about POSIX-type shell syntax in his overview of fish in LWN.net, online at http://lwn.net/Articles/136232/. His design document, which makes thought-proviking reading for shell gurus, is at http://roo.no-ip.org/fish/user_doc/design.html.

Why fish?

Most readers of this column are comfortable with traditional shells, so shells that are “user-friendly” (meaning simple or limited) may not seem very useful. Still, the traditional interface has lots of room for surprisingly-useful extensions and changes to make the shell more consistent and simpler. So a “friendly” shell like fish can be worth a look. (And, as we’ll see, fish really isn’t that limited.)

For example. the syntax highlighting in fish is a great way to avoid errors like misspelled command names, invalid options, and unmatched quotes. Moreover, Tab-completion — for commands, options, and more — is more complete than in many shells, and you may find quoting in fish easier to understand than other shells’ implementations.

Finally, because fish was designed from scratch, the interface is more consistent, and its semantics are more like a normal programming language. For instance, variables have scope, and setting and managing variables and arrays is simpler. fish also doesn’t use subprocesses to isolate difficult-to-handle jobs like command substitution, so you don’t have to remember the strange ways that a subprocess interferes with the shell’s state.

Starting fish

After you’ve installed fish — from one of the packages or from source at http://roo.no-ip.org/fish/ — you can start it by typing fish at the prompt from another shell. To go back to your original shell, just type exit. Later, after you test your installation, you might decide to make fish your default shell. (Shell-switching works both ways, of course: if fish is your default shell, you can drop into the Z Shell temporarily by typing zsh at a fish prompt.)

Tab Completion

Many shells have Tab-key completion for command names, filenames, and more. Fish does, too, but its Tab completion stands out because it includes a description of each file’s contents and each option, because it can complete wildcard and curly-brace expressions, and because its completion of cd takes CDPATH into account.

In Listing One, the first command-line is rm and a space. Because rm takes a filename argument, pressing Tab at this point lists the four entries in this directory and describes each one. In the second command-line, man what followed by a Tab lists the man pages that begin with what.

Listing One: Examples of fish Tab-key completion

jpeek@hax ~> rm TAB
X.gz (Gzip archive) jm.html (HTML page)
g.xwd (X window image) man/ (Directory)
.
jpeek@hax ~> man whatTAB
whatis (1: display manual page descriptions)
whatnow (1: prompting front-end for sending messages)

As in other shells, fish repeats the command line after showing the possible completions.

Figure One has more examples. It starts with a Tab completion of an option. Type echo - to start the option, then press Tab to get a list of echo options. fish helpfully explains each option. (When the list is too long to fit the screen, you can scroll it with Page Up and Page Down, the arrow keys, and the space bar; pressing any other key exits the list and puts that character onto the command line.)

Figure One: fish completion and syntax highlighting



Syntax Highlighting

Syntax highlighting can be handy when you’re programming. Misspell a keyword, or leave an unmatched brace, and the colors on your screen let you know right away. fish has command-line syntax highlighting that’s handy and even addictive.

You’ve seen that command names seem to be shown in green. When you start typing a command name, it’s actually red. It “turns green” as soon as you’ve typed a valid command name.

Let’s go back to Figure One and follow the examples. If you type echo - and then Tab and then p, fish turns -p red, indicating that it’s an invalid option. You can erase the -p and continue typing. Arguments are colored white because they aren’t checked. Pressing Enter runs the command-line.

The next echo command shows unmatched quoting. As you type a quoted string, it stays red until you type a matching quote. If you omit the matching quote and press Enter, the fish tokenizer prints a useful and specific error message.

Figure Two shows a complicated expression with braces. As you type the closing character of a pair — brace, quote, and so on — fish highlights both of them in light blue. This also happens when you’re editing a command line, as shown at the end of Figure Two: the brace under the cursor and its matching brace, earlier, are highlighted.

Figure Two: The fish shell showing a matched pair



Launching the Application for a File

You can launch graphical applications from any shell prompt (not just a fish prompt) by typing the program’s name instead of by choosing it from a menu or clicking a button. One advantage of this is that you can see errors and other trace output that’s often “eaten” by a window system. And, of course, you can suspend a program or kill it using your shell’s job control.

Opening a file with a graphical application isn’t always easy, though: which application goes with which file, and what command-line syntax does the application expect? fish handles those details for you with its built-in open command. Type open filename, and fish consults the same database and .desktop files that KDE and Gnome use to find the proper application and command-line syntax.

Directory Stacks vs. Directory History

Most shells have directory stacks that you can manage by hand with pushd and popd. So does fish. Most shells also remember your previous working directory. Typing cd - (cd with an argument of a dash) takes you back. fish does this, too.

But fish goes one better with its directory history. It’s a list of directories that’s maintained automatically, containing all of the directories you’ve visited (unless you clear the list). To see the list, use dirh. Typing nextd changes your working directory to the next one in the list, and prevd moves to the previous directory.

Figure Three: The directory history



Figure Three gives an example: the current directory is shown in a blue-green color. Typing prevd moves to the previous directory, /tmp. (When the command line is empty, you can also use Alt-Left and Alt-Right to move backward and forward through the directory history, respectively.)

Simpler Block Syntax

In fish, blocks start with if, switch, for, and so on, as they do in other shells, but fish does away with the Bourne shell’s special words do, in, and then. The switch statement doesn’t use parentheses after patterns or double semicolons (;;) after each test, as the sh case does. All blocks end with end instead of fi, esac, and so on.

Listing Two shows a function named makepnm that uses switch to test its first argument and run a netpbm conversion program (for more about netpbm, see the August 2004 Power Tools column titled “Fix Images Fast with Netpbm”.)

Listing Two: A fish function and switch statement

function makepnm
switch $argv[1]
case '*.jpg' '*.jpeg'
jpegtopnm $argv[1]
case '*.tif' '*.tiff'
tifftopnm $argv[1]
case '*'
echo makepnm: cannot handle $argv[1] >&2
end
end

Arguments to a function are passed through the array $argv. Each case in the switch tests the string $argv[1], the function’s first argument, against one or more strings or patterns. The final test, case '*', is an optional, “default” test to catch arguments that previous case tests didn’t match.

(In most Bourne-type shells, you must put double quotes around arguments like $argv[1] to keep the shell from splitting the argument’s expanded value at spaces. This isn’t needed in fish, and, in fact, you shouldn’t do it. If you need to split a string at whitespace, use the tokenize command. Also see the section “Quoting” later.)

You can enter a block at a shell prompt, but it’s not as easy in fish as it is in other shells: the block has to be entered on a single line, separated by semicolons, like this:

function makepnm; switch $argv[1]; case .

Until this is fixed in a future release of fish, it’s easier to enter long blocks by putting them in the fish startup file (~/.fish or in a separate file that you write with a text editor and read into fish with its . (dot) command. For instance, if you put the makepnm function from Listing Two in a file named func, you could read it into fish by typing this at a shell prompt:

jpeek@hax ~> . func

I/O Redirection

The fish input/output redirection syntax is full-featured but also different than other shells. There’s a complete description in its built-in help (which you can also read from the home page http://roo.no-ip.org/fish/).

One example of redirection is the echo command line near the end of Listing Two. The >&2 operator routes output onto standard error.

Version 1.13 of fish added a feature that Bourne-type shells have had: redirecting the input or output of an entire block. Let’s see a two-part example.

Listing Two: The makepng() function

function makepng
switch $argv[1]
case '*.jpg' '*.jpeg'
jpegtopnm $argv[1] | pnmtopng
case '*.tif' '*.tiff'
tifftopnm $argv[1] | pnmtopng
case '*'
echo makepng: cannot handle $argv[1] >&2
end
end

The makepng function in Listing Two is the makepnm function with a pipe to pnmtopng added in two places. It lets you convert a TIFF or JPEG file to a PNG file like this:

jpeek@hax ~> makepng foo.tif > foo.png
jpeek@hax ~> makepng bar.jpg > bar.png

The netpbm utilities can convert many other formats. You could imagine adding lots of other case s to the switch to handle each of the input formats. However, the code in Listing Two requires each case to have a pipe to pnmtopng. Because you can redirect the output of a block, you can rewrite and simplify the switch, as shown in Listing Three.

Listing Three: A revision of the makepng() function

function makepng
switch $argv[1]
case '*.jpg' '*.jpeg'
jpegtopnm $argv[1]
case '*.tif' '*.tiff'
tifftopnm $argv[1]
case '*'
echo makepng: cannot handle $argv[1] >&2
end | pnmtopng
end

Ending the switch with a pipe to pnmtopng filters the output of whichever case is chosen. (The output of the default case isn’t filtered because the echo writes to standard error. A pipe only redirects the standard output.)

Quoting

Quoting in fish is much simpler than in other shells: putting quotes around a string disables all interpretation and expansion. And there’s no difference between single- and double-quoted strings. If you need the shell to interpret something — to expand a variable, for instance — leave that something outside quotes. This means that a command line can have a mixture of quoted and unquoted sections. Listing Four shows an example.

Listing Four: Quoting and redirection example

for f in *.jpg
set out (basename $f .jpg).png
makepng $f > $out
echo '<img src='\“$out\”'>'$out'<br>'
end > index.html

In Listing Four, a for loop steps through each of the JPEG files in the current directory. First, it uses the Linux basename utility to strip the .jpg extension from each filename by using a subshell (which is how fish does command substitution); it stores the result, with .png appended, in the shell variable $out. Next, it calls the makepng function and stores its output. The echo command creates a line of HTML that displays the image and a caption (its filename). Because a quoted string can’t contain a quote, we’re using the escape sequence for a double quote character, \", instead of a literal double quote.

Listing Four yields HTML such as:

<img src=“foo.png”>foo.png<br>
<img src=“bar.png”>bar.png<br>

The output of the entire for loop has been redirected to the index.html file. Because the echo command is the only one writing to the standard output without redirection, its output goes into the index.html file.

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]