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.
iceis a command that modifies the next command (why not just add a modifier to the command itself? Who knows.) forallows you to execute a command as a loop (like you’d expect) without having to separateicefrom the actual command. Helpful if you don’t need separate ice modifiers for each commandlucideliminates 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
zinitsyntax. zi updateto update all plugins
- The syntax is strange.
- Packaging something as a plugin is super simple. Create a
name.plugin.zshfile 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 -vand 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_nameto inspect types of various variables. - zsh line editor is
zle.zle -N widget-nameadds the widget to the line editor so you canbindkeythe widget. bindkeylists out all of your keyboard shortcutszle -lalists out all ‘widgets’ (zsh commands, not sure why they are called widgets). You can bind keyboard sequences to these widgets.- The
edit-command-linewidget ‘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$PATHin 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' | catis equal tocat <<< 'hi'). zsh also has here docs with the standard<<EOLsyntax.- Nifty command to list out all autocompletions.
zinitalso 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
bindkeyconfig to get substring history search working zsh-autosuggestionscaused 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_WIDGETSfixed 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.
fasdis a really neat tool, but I wanted to customize thejshortcut 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/