The Fish Shell

The fish shell is a recent attempt to improve upon the traditional UNIX shell design. The design strives to simplify shell syntax while making the general behavior of the interactive sessions more user-friendly through verbose error messages, informative and context-sensitive tab-completions, etc.

It's easy to ignore the importance of a program being user-friendly. To make a UNIX command shell user-friendly is a fairly difficult task as well, since the interface to tools used in the shell is beyond the shell author's control. Nevertheless, fish makes an impressive effort: the interactive fish session can not only tab-complete the names of command arguments, it provides information about what the arguments do. Likewise, the shell provides a helpful set of error messages to help people who are transitioning to fish from other shells.

Syntactically, the shell strives for simplification: fewer specialized syntactic forms, with a greater emphasis on a smaller core set of syntactic rules and the use of sub-commands to do more complex evaluations. Rather than bash's mix of infix operators, commands, arithmetic blocks, conditional blocks, etc., most fish evaluations rely on just two rules: commands are a command name followed by arguments, and parenthesis enclose sub-commands (like $() in bash) as a way to fill in arguments.

The simplified syntax of fish does seem like generally a good thing: bash forms like $(), $(()), and [] always seemed a bit vague to me. The decision in fish to give different conditional and iterative forms similar syntax (as opposed to bash's fi, esac, do/done, etc.) seems a great improvement as well. However, I wonder if fish's approach might still be a bit too cryptic as well. Limiting the available syntax can make the syntax of the language easier to understand: but it can be taken too far. A bit of redundancy in the syntax can also make the language clearer, by letting people express things more naturally.

One of the improvements I most enjoy in fish is that if a single value is passed into a command, it is never converted into multiple values. For instance: set a "x y" count $a In bash, "count $a" would be equivalent to "count x y": the one variable passed in would be split on whitespace and interpreted as two separate arguments to the command count, unless the caller put quotes around the variable $a in the call. This policy was apparently intended to work around the fact that early shells didn't support array values, but as a result the user becomes responsible for ensuring that arguments are never split when they shouldn't be. The value of $a is essentially re-evaluated as though the user had typed it, making it difficult to achieve predictable performance in certain cases.

In fish, the same command would be equivalent to "count 'x y'" - and so the original nature of the string as a single value is preserved. Users wishing to store multiple arguments to be passed to a command may create an array: passing an array as an argument to a function causes each entry in the array to become a separate argument to the command. This policy helps to prevent problems processing data which may include whitespace.

Another nice feature of fish is its "universal variables": variables set with the -U option persist across multiple instances of fish and even propagate to other running copies of fish as soon as they are set or modified. This provides a very handy way to maintain and alter settings for fish or other tools. However, without a way to divide the namespace, it seems like use of universal variables as a means of configuring programs would quickly cause a lot of clutter.

fish's policy of eliminating syntax and built-in functions in favor of commands on the PATH does have some complications: for instance, where another shell might use a special syntax in order to determine the size of an array, fish relies on the count command, which counts the number of command-line arguments it is given. This means that to get one little piece of information, the size of an array, the entire array must be split into command arguments and passed through execve() - and copied as a result, just so count can yield argc. As a result, fish's ability to tell you how big your array is depends on the maximum number of command-line arguments your operating system supports.

Overall, I find fish to be a very interesting example of what a UNIX command shell can be. In my own efforts to design a shell I have all kinds of ideas I think could do the job better, but there are still so many unresolved issues it's hard to tell what the final shape of things will be. fish is already at the point of functionality, so I can see what they've achieved and how. I look forward to learning more still.

George Caswell
Last modified: Wed Nov 21 18:36:00 EST 2007