At some point last year I fell in love with the
fish shell. It features almost zsh-like power, very nice editing and searching, for example autocompletion like in a browser. I’ve been using it as my primary command line ever since.
I’ve also switched to using
fish for scripting, rather than bash. This has a great advantage, and also a disadvantage: The bad thing is that its syntax is not
sh-compatible. The good thing is that its syntax is not
sh syntax is of course time-tested and much beloved by many users and admins. But if we’re really honest, it’s a bit of a mess.
fish improves on this a lot. Have a look at its tutorial to see things like in-terminal syntax highlighting, sane arrays, sane functions and plenty more.
sh being a standard and
bash being nearly everywhere, there is a lot of stuff out there that blindly assumes it can just dump
sh-style commands into e.g. an
ssh connection and have things work on the other end. The culprit that annoys me the most is
ssh-copy-id — when I changed my login shell to
fish on a server, it broke down, because
fish didn’t understand what that script wanted.
There’s an easy fix for that bug in the script: It could simply make
ssh launch an
sh-compatible shell before pouring in the commands. This fix is in the works in upstream, but at least in Ubuntu 15.04 it’s not available yet.
That said, I wanted a general fix anyway, because there will certainly be other scripts that try to do this.
fish will never be fully
sh-compatible, and I don’t mind that. I like
fish-style scripting. So I want to make the server smart enough to figure out what is going on. It turns out that that is pretty simple.
This is what I want to achieve:
sshI want to end up in a nice interactive
fishshould act as a login shell so things like the automatic
byobulauncher in Ubuntu still work.
fish, the connection should terminate instead of dropping me into an outer shell.
fishshould not be started. Instead, something
sh-capable should run, for example
bashinteractively, by running
Here’s how I did it:
I changed my login shell back to
$ chsh -s /bin/bash
I added the following at the end of
case "$-" in *i*) fish -il; exit ;; *) ;; esac
What does this little monstrosity do?
Since this is in
~/.bash_login, we know we are a login shell. “Manually” launched
bash instances will not run this script and so will behave normally.
bash stores some of its runtime flags in
$-. If it’s an interactive shell, that string will contain
i. Given that here we are in a login shell and combining that with the
being present, we know there is a human who just logged in and who has a keyboard to do interactive
things. Let’s give him a
-i argument makes sure
fish will be interactive too, and
-l tells it to behave like a
fish exits, we call
bash will exit too, so the user will not even
notice that there is a
bash involved in all this.
In the other case, when
bash is not interactive, we now know that there is a script connecting to
our server. We do nothing and let
bash start up normally, so even bad scripts like
will find their familiar environment and just work.