Xonsh is no longer a possible replacement for zsh

A few weeks ago I wrote about my dissatisfaction with zsh. I decided to take a close look at xonsh and fish.

I decided to try xonsh first because I’m a Python aficionado (I’ve been using it as my primary language for eight years). The idea of using all of my favoriate Python language features and standard library along with the ease of launching external commands with I/O redirection and pipelines was intriguing. Because xonsh is currently at version 0.2.3 it was clear that the author didn’t believe it was ready for primetime. Nonetheless, I was willing to give it a try as the documentation and mailing list suggested it was in good enough shape for a software engineer like myself who is used to using software that is rapidly changing.

The first thing I did after installing it was run a few external commands and python statements. I then showed the command history using the builtin history command. So far, so good. I then tried showing the most recent command in the history using history show -1. That worked fine. Okay, let’s do something a little more challenging; show the most recent five history entries:

$ history show -5:
usage: history [-h] {show,id,file,info,diff,replay,gc} ...
history: error: unrecognized arguments: -5:

Oops! The documentation says that should work. The inverse, showing the first five entries with history show :5 does work. So I sent a mesasge to the mailing list. Almost immediately the creator of xonsh responded with a proposed fix in the form of a github pull request. Looking at the proposed fix I saw my first red flag. The fix was a kludge and included no unit tests to ensure that the fix was correct and to keep regressions from occurring. I countered with my own pull request that included extensive unit tests for the bug I was fixing.

I was happy to see that the author cares about fixing bugs in a timely manner. I was less happy that his proposed fix was something I would barely tolerate from a summer intern. Worse, before creating my fix I ran pylint against the module. OMFG! Pylint gave it a score of 7.13 out of 10. Worse, pylint pointed out two outright bugs due to misnamed variables. The only way to tell if my change introduced more lint was to diff the lint output before and after my fix was commited. That is totally unacceptable. Too, the entire code base was riddled with problems ranging from the nitpicking trivial (trailing whitespace on a line, lines too long) to serious (missing doc strings, too many references to protected object members).

I was also deeply troubled by such bogosities as injecting __xonsh_history__ and __xonsh_env__ into the builtins module scope. Not only that but those are the primary means of accessing xonsh configurable settings. Ugh! For example, from the history.py module:

data_dir = builtins.__xonsh_env__.get('XONSH_DATA_DIR')

Despite that rocky start I thought there was enough positive things about the project to try using it and contributing to its improvement. But the code would have to be cleaned up before I would make any other substantive changes. So I asked it would be okay if I contributed a sequence of changes to make the code lint clean. Getting the green light I submitted several lint cleanup pull requests. Getting each one accepted was a challenge. Primarily because the project owner isn’t really interested in having lint clean code. The final straw that made me decide to give up on the project as hopeless were two cleanups the project owner objected to.

The first was changing

if len(inp) == 0:


if not inp:

Anthony told me to revert that because he didn’t like the negative conditional. The second was changing

for d in filter(os.path.isdir, path):


for d in (p for p in path if os.path.isdir(p)):

Anthony’s objection this time was that he prefers filter and map to list comprehension and generator functions. When I countered that filter and map are deprecated he said in effect “why are they builtins if they’re deprecated”. All you have to do is Google “python map filter deprecated”. One of the two five results is this article by Guido van Rossum proposing that map, filter, and reduce be removed from python3. There’s plenty of other web pages that also make the case for not using them.

In other words, Anthony tries to use Python as if it were a different language. He doesn’t appreciate that there is such as thing as idiomatic Python. Anthony believes that just because you can achieve a result in more than one way there is no reason to prefer one alternative over another other personal preference. Sorry, but I can’t contribute to a project with those ideals.

So I’m off to try fish.

P.S., I forgot to mention two other things that gave me a WTF moment. The first was when I noticed that the directions for running the unit tests did not test the code in my local git repository — it tested the code installed by pip. Running the tests were also not hermetic — they were affected by my local ~/.xonshrc file. Too, I couldn’t just type “make test” to execute the tests. So I decided to fix all of those problems. In the process of getting my changes accepted Anthony told me he wrote the directions to specifically test the installed code, not the code in his git repository and he didn’t understand why anyone would test their uncommitted code. So either he doesn’t test changes before committing them or he regularly installs and runs untested code.

Second, the history subsystem is weird. Don’t take my word for it, go read the xonsh history documentation. Notice too this only partially correct assertion in the first paragraph of that page: “This is saved when the shell exits”. Okay, that is true of bash but it’s not true of many other shells sharing the same Bourne shell lineage such as zsh. Also, bash provides history -a to manually save the history and history -r to read it so you’re not limited to writing the current shell history only when it exits. Yes, yes, that’s a pretty braindead approach so I agree with Anthony regarding bash but other shells like zsh have managed to implement a sane solution without requiring xonsh’s ridiculously over-engineered solution.