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-compatible.
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.
However, with 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:
ssh I want to end up in a nice interactive fish.fish should act as a login shell so things like the automatic byobu launcher in Ubuntu still 
work.fish, the connection should terminate instead of dropping me into an outer shell.ssh-copy-id connects, fish should not be started. Instead, something sh-capable should run, for example bash.bash interactively, by running bash from fish.Here’s how I did it:
I changed my login shell back to bash:
 $ chsh -s /bin/bash
I added the following at the end of ~/.bash_login:
 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 i flag 
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 fish.
The -i argument makes sure fish will be interactive too, and -l tells it to behave like a
login shell.
Finally, once fish exits, we call exit so 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 ssh-copy-id
will find their familiar environment and just work.