Migrating from bash to zsh
Categories: Software
I love productivity tools. Anyone who works with me knows I love my keyboard shortcuts and tiny productivity hacks. Small, incremental productivity improvements add up over time: feeling fast makes you fast. Plus, I just enjoy tinkering and making things more productive.
One of the rabbit holes I love to go down is optimizing my development environment. I spend a lot of time in a terminal, so it’s a fun place to optimize my setup. Ever since hearing of Oh My ZSH I wanted to try out zsh, so I set aside some time to update my dotfiles to use zsh as the default shell.
Below are some notes & learnings from the transition.
What’s new in zsh?
- There are lots of small packages out there for neat things like autocomplete, async prompts, etc. This is the best part about zsh and the main reason I put the effort into switching.
- There’s a bunch of configuration managers out there. Oh My ZSH, zplug, antigen, antibody, zinit, etc. These managers pull various bundles of zsh scripts together and source them for you.
- Antibody was the best manager that I could find (when I originally wrote this post in 2020). Allows you to pull directly from GitHub repositories, and load shell scripts that aren’t packaged as a "plugin". However, in less than a year it died out and is unmaintained. Here’s my plugin list with antibody
- Zinit looks like the best package manager nowadays (2021). Here’s how I moved from antibody to zinit and the change that enabled turbo mode.
- The syntax is strange.
ice
is a command that modifies the next command (why not just add a modifier to the command itself? Who knows.) for
allows you to execute a command as a loop (like you’d expect) without having to separateice
from the actual command. Helpful if you don’t need separate ice modifiers for each commandlucid
eliminates the loading messages. Not sure why this isn’t enabled by default.- I found this example setup to be the most helpful in decoding the
zinit
syntax. zi update
to update all plugins
- The syntax is strange.
- Packaging something as a plugin is super simple. Create a
name.plugin.zsh
file in your repo. This file is autoloaded by plugin managers. - I’ve always struggled to understand where I can map key pressed to the strange double-bracket definitions I see (e.g.
^[[A]
is equivalent to the up arrow key). Run/bin/cat -v
and when you press a key it’ll output the key definition you can use in key bindings. - There are many options for up/down history matching. I like the substring search package, but there are great builtins for this as well
- There are many little changes to the shell which make life easier. For instance,
mv something.{js,ts}
will rename a file. - zsh variables have different types. Run
type var_name
to inspect types of various variables. - zsh line editor is
zle
.zle -N widget-name
adds the widget to the line editor so you canbindkey
the widget. bindkey
lists out all of your keyboard shortcutszle -la
lists out all ‘widgets’ (zsh commands, not sure why they are called widgets). You can bind keyboard sequences to these widgets.- The
edit-command-line
widget ‘parks’ the current command until the next command you type is done executing. Here’s how to bind to ctrl-e (the default ctrl-q binding wasn’t working for me). - Function path is
fpath
, the list of paths to search for the definition of a function definition. This is distinct from$PATH
in zsh. - A big improvement with zsh is the ability to async run commands. For instance, you can display your prompt and then run various git commands and update your prompt status. This is critical for large repos (where git commands can take seconds to run) and is the main reason I switched to zsh.
<<<
is a here string. Useful for passing a string to stdin (echo 'hi' | cat
is equal tocat <<< 'hi'
). zsh also has here docs with the standard<<EOL
syntax.- Nifty command to list out all autocompletions.
zinit
also has a similar (cleaner) commandzi clist
. - Snippet to list aliases, functions, and variables.
- Globs support regex-like syntax. It’s worth spending some time reading about this and getting familiar with it.
- There’s a neat trend of folks rewritten common utilities (cd, cat, find, etc) in rust. Here’s a great writeup of improved utilities you can use. You can find my set of tools here.
Plugins
Some notes on my plugin configuration:
- Here’s my list of zsh plugins.
- It took some extra
bindkey
config to get substring history search working zsh-autosuggestions
caused weird formatting issues when deleting and pasting text (the autocomplete text wouldn’t use a different color and I couldn’t tell what was actually deleted). ModifyingZSH_AUTOSUGGEST_IGNORE_WIDGETS
fixed the issue for me.- I tried to get larkery/zsh-histdb working (really neat project) but it doesn’t play well with the fzf reverse-i search, which I really love. Hoping to give this another go in a year or so to see if the integration with fzf and other standard tooling is improved. Being able to filter out failed commands from your zsh history search would be neat.
- zsh-autosuggest and bracketed paste don’t play well together. This snippet fixed it for me.
fasd
is a really neat tool, but I wanted to customize thej
shortcut to automatically pick the first result. Here’s how I did it.
Resources
Some helpful posts and guides I ran into:
- Really awesome guide to fancy zsh features & syntax https://reasoniamhere.com/2014/01/11/outrageously-useful-tips-to-master-your-z-shell/
- https://remysharp.com/2018/08/23/cli-improved
- https://github.com/unixorn/awesome-zsh-plugins
- https://scriptingosx.com/zsh/
- https://sourabhbajaj.com/mac-setup/iTerm/zsh.html
- http://zpalexander.com/switching-to-zsh/
- https://chenhuijing.com/blog/bash-to-zsh
- https://medium.com/rootpath/replacing-bash-with-zs…
- http://jeromedalbert.com/migrate-from-oh-my-zsh-to-prezto/
- https://terminalsare.sexy/#tools-and-plugins
- https://callstack.com/blog/supercharge-your-terminal-with-zsh/