Shipping completions for Go CLIs using GoReleaser and Cobra
Everyone likes command line completions, so much that some even install extra tools just to …
Back in June, I started porting my dotfiles from ZSH to Fish. Here’s why.
Fish’s performance is a lot better than ZSH’s, and very similar to Bash.
We can verify that by firing up a container with limited CPU and memory, like so:
docker run --rm -it --cpus 1 --memory 100m ubuntu bash
And then we can install all the shells, as well as hyperfine, and see how they go:
apt update apt install zsh fish wget -y wget https://github.com/sharkdp/hyperfine/releases/download/v1.11.0/hyperfine_1.11.0_amd64.deb dpkg -i hyperfine_1.11.0_amd64.deb for s in bash zsh fish; do hyperfine --warmup 3 "$s -i -c 'exit 0'" done
The results are very impressive:
I honestly never though ZSH was this slower compared to Bash… maybe it got slower over the years… I don’t know, but once you use fish the first time, you’ll notice it.
On both Bash and ZSH, you’ll probably end up with a whole lot of
source something.sh, which, once added up, can slow your shell quite a bit.
You also need to set your aliases, environment variables and etc, every time you start a new shell. While this is helpful to debug things, it is not very fast.
Fish allows you to set universal variables, which are shared across all shells and system restarts. You don’t need to set them on every shell init, instead, you set them once (
set -U) and they will be added to your
It also has lazy loading of completions and functions: you just put them in the right folders (
~/.config/fish/functions), and it loads them when you first try to use them, instead of every time you open a new shell.
This can all be confirmed by comparing my Fish setup’s performance (it has some plugins, abbreaviations, completions, functions, etc) with empty ZSH and Bash:
I open new shells literally hundreds of times a day, I don’t want to waste those milliseconds.
Of course, performance is not the only unit of measure, otherwise I would use just plain Bash (or SH), but if I can get the same features faster… that matter a lot to me.
Some of the features that you’ll need plugins on ZSH/Bash, and are native on Fish:
Because of that, I only need a couple of plugins:
fzf(for CTRL+R history search and other goodies)
grc(colorize output of several commands)
Other than that, I just put a couple of files in their right folders, setup a bunch of abbreviations, and that’s it.
I use Fisher, because I don’t really need the whole plugin framework thing from oh-my-fish, but both of them are very good and you can choose whichever you like more to manage your plugins.
Fish does not talk POSIX shell, so, some things are different.
Its common for me to script my way into the shell itself when dealing with issues, so, in the beginning, I struggled a bit. If you usually use just plain commands and pipes, it might not be an issue for you.
for loops and
() instead of
$() took me a while to get used to, after I don’t know how many years of writing things that way, but, once I got used to, it is actually simpler.
Fish doesn’t have the concept of aliases, instead, you either wrap your command in a function (Fish has a function that does that for you, which is called
alias), or use abbreviations.
Abbreviations expand once you type them, which is actually better in some ways:
$3 and etc, on Fish we have
It’s not that big of a deal, but I always forget about it. 🤷♂️
Instead of doing:
On Fish you need to do:
set FISH bar
The cool thing is that it has options to append and prepend to lists, so you don’t need to do things like
PATH="/path/bin:$PATH and can just do
set -p PATH /path/bin instead.
It’s worth mentioning that when running one liners, the plain shell syntax works just file, e.g.:
FOO=bar echo $FOO
The Fish website has a lot of good docs, I suggest you start from there.
You can also take a look at how I manage my Fish dotfiles for inspiration (or just fork and change to match your own taste).
You can also play with it online, without installing anything!