Time to pick a new shell: fish, xonsh, elvish, bash, zsh, ksh93


Why aren’t there any good alternatives to bash or zsh? Specifically, a OS CLI shell that does not suffer from the problems inherent in being compliant with the POSIX.2 (aka POSIX 1003.2) standard? And also doesn’t suffer from the other problem that bash and zsh have due to all the configurable behaviors that make it effectively impossible to predict how those shells will behave?

Two years ago I got fed up with zsh and wrote a blog post why. That caused me to look for a saner alternative. I considered xonsh because it is based on Python which is one of my two favorite programming languages (the other being C). But I gave up on Xonsh fairly quickly. I eventually settled on Fish.

I had some misgivings about the Fish project because, as of Sep 2015, it had numerous open pull-requests. Including several more than a year old and quite a few more older than three months. The source code (both C++ and functions written in its native script language) was also a mess with no coherent style and hundreds of lint errors. At least two lint errors identified actual bugs and most of the remainder pointing out code that was hard to understand. Too, there were a couple hundred open issues more than three years old which even a cursory review suggested were no longer relevant. Nonetheless, I chose it as my new day to day shell and started contributing changes to the project. Primarily because its explicit non-adherence to the POSIX 1003.2 standard for shells meant it had far fewer surprising behaviors. It also had several innovative ideas such as every variable being an array even if it contained only a single element.

Near the end of 2015 I was offered commit privileges; that is, becoming a member of the core development team. I demurred at that time because I wasn’t sure I was going to use Fish long term. But in late January 2016 I accepted the invite to join the core dev team. Primarily because the development team expressed no objections to my requirement that the code style be standardized and changes to deal with oclint and cpplint errors would be merged without debate.

I spent several months doing things like running the code through clang-format and oclint and fixing problems they found. As well as making it easy for everyone else to do so by adding new build targets like make style and make lint. Once the code was in a better state I felt more comfortable making more substantive changes.

Fast-forward to today where I am looking for a different shell to be my day to day interactive shell. Why? There are several reasons. Such as the fact that the Fish model for I/O redirection and pipelines is broken and results in FAQs from people who expect saner behavior provided by nearly every other shell. Broken behavior I accepted till now because I hoped that such problems would be fixed sooner rather than later. I no longer believe that will happen. Again, why? Because there have been too many arguments over bike-shedding issues like whether all uppercase var names like FISH_HISTORY and FISH_VERSION should be renamed to lowercase. Or, in the case of FISH_VERSION, renamed to version because one developer is a fan of csh (actually tcsh although they never use that term). More importantly no one but myself seemed to be interested in setting a consistent vision for future releases with milestones and a roadmap.

Another example: Fish issue #478 was opened five years ago by xiaq to suggest improving how commands to options are handled. Keep in mind the ID of the person who opened that issue as it’s important to this blog post. I commented a year ago asking if we should implement a Fish compatible getopt command. Fast forward to today. I opened a new issue asking for feedback on a different approach to issue #478. Mostly because it seemed like the DocOpt based solution would never be implemented. Too, I was not convinced the DocOpt idea was sound. I implemented the argparse command and the feedback was uniformly positive and resulted in several subsequent improvements. The Fish developer I’ve clashed with from the day they were granted commit privileges then reopened issue #478 and the leader of the fish project stated they still wanted to try and implement that solution. I’m wondering if I’ll die of old age before that happens. Especially since it has been more than two years since the last substantive commit to the Python DocOpt project which has been open five years, and was the basis for the fish-shell implementation, and it still hasn’t reached v1.0 release status.

I have no intention of going back to either bash or zsh as my day to day interactive shell. They have too much baggage and broken behaviors. Zsh in particular suffers from a lack of a coherent vision which has lead to the project incorporating too many incompatible behaviors via configurable options.

So I’m once again looking for an innovative shell that I could use on a daily basis and would be willing to contribute non-trivial changes to the project. I took another look at Xonsh but was put off by the fact it has several pull-requests more than a year old and most of the others are more than a month old. Too, it has recently implemented things like the cat command as a builtin which seems rather pointless.

The current candidate for my new day to day shell is Elvish. It is even more extreme in its departure from traditional POSIX.2 shells than fish compared to shells like ksh, bash, and zsh. But therein lies its strength. Elvish is based on sound programming language principles rather than the adhoc mess that is the original Bourne shell and all subsequent, POSIX.2, based shells. My primary concern is how it handles external commands that exit with a non-zero status. At present it turns that into an exception. Which means Elvish behaves as if bash/ksh/zsh was running with set -o errexit in effect. This is great from a safety perspective. The problem is that there are a large number of commands which exit with a non-zero status for non-fatal situations. The grep command, for example, exits with status of one if no lines matched the pattern. That is rarely a fatal error that deserves raising an exception.