New Year, New Shell
Up until now I have stuck with Bash as my preferred shell environment predominantly for its ubiquity. There are well-known alternative shell projects such as zsh, which build upon Bash with an “add more” attitude.
Fish, on the other hand, is a significant departure from Bash, released in 2005 with the brilliant slogan, “Finally, a command line shell for the 90s”.
The need for a plugin framework or patched nerd font to configure my shell environment never really appealed to me. With Bash I had only built up a humble selection of aliases, exports and functions, with the aim of minimalism and portability (setup on new systems involves simply running stow bash from my dotfiles repository). With fish I’ve ended up needing to configure less things, and what I have is noticeably cleaner.
Features and Impressions
As you type, fish offers inline completion for your expressions, including an optimistic default completion where possible using entries from history. It’s addictive.
- Use Tab to select an option for the first-level completion.
- Use Ctrl+F to fully expand the current completion.
- Use Up to cycle through history, scoped by what you’ve entered so far.
One awesome command to use with completions in fish is
abbr. This is an optional alternative to
alias that instead will expand a user-defined shortcut when used.
abbr -a -g rsync 'rsync --progress'
Any .fish script added under ~/.config/fish/functions will be autoloaded and available in your shell.
Compared to bash, I find the scripting syntax for defining functions in fish slightly more elegant. I also love that you can include a description that will appear when browsing for the function in the completion UI.
function fe --description 'Open fzf and edit file on selection' fzf | read -l result if [ $result ] $EDITOR $result end end
Fish generally uses functions over variables. For instance, you can create your own implementation of
fish_prompt (and there’s a
fish_right_prompt!), or redefine
prompt_pwd which this composes. To me, this makes sense and feels more well-designed.
There’s also more room to rejoice in working without raw terminal escape sequences!
set_color supports both named colours and hex values, and
bind has simple expressions for modifier keys such as Ctrl (
Fish also offers events, a higher level abstraction to define functions that are event handlers, triggered via
Docs and Configuration
Fish reads user-level configuration from ~/.config/fish/config.fish. Here, you can run any command on init to override variables, set key bindings, define aliases, and source other files as in Bash. Fish will also read ~/.profile and ~/.inputrc by default. Lovely.
Fish also offers a web app to configure itself via
From a pure practicality standpoint, it’s important for me that most everyday expressions and commands overlap between fish and Bash. On balance I think fish has done right in this regard.
>>] and most commands remain identical to Bash, which is a large enough piece of the equation in daily use. However, scripting as we have seen quickly exposes fish’s syntax differences, and Bash scripts will be mostly incompatible.
Not all commands are equivalent either. Notably,
echo behaves differently around variable interpolation and quotes. Command substitutions use brackets only rather than
Saying this, it has not been a hard process to migrate my Bash customisations to fish.
After one week of use and adaptation, I ended up switching shell for good. The experience isn’t life changing, but fish provides great creature comforts out of the box and feels genuinely more modern in use and in configuration.
Shell differences have been nearly a non-occurrence, especially when one is mostly spending the day using Unix commands. At the same time, I will continue to maintain versions of my scripts for Bash, but configure new stuff for fish on my development hosts.
If you’re interested in any of the settings shown here for fish, feel free to browse my dotfiles repository here.