If you use the terminal a lot, you can probably understand the frequent pain of typing out long commands. Bash’s tab completion is ineffective, file paths are tedious to write, and typos are a persistent annoyance. I have three good reasons why Zsh addresses these and boosts your terminal workflow.
A vast plugin ecosystem to solve thousands of problems
Using shells in their default state is a cumbersome experience because they invariably lack the useful and ergonomic features we need to be truly productive. For example, typing out long commands can be laborious and unforgiving when you make mistakes. We need a way to functionally augment the capabilities of our bare-bones shells and provide them with features that make them significantly easier to use.
To provide our shells wit…
If you use the terminal a lot, you can probably understand the frequent pain of typing out long commands. Bash’s tab completion is ineffective, file paths are tedious to write, and typos are a persistent annoyance. I have three good reasons why Zsh addresses these and boosts your terminal workflow.
A vast plugin ecosystem to solve thousands of problems
Using shells in their default state is a cumbersome experience because they invariably lack the useful and ergonomic features we need to be truly productive. For example, typing out long commands can be laborious and unforgiving when you make mistakes. We need a way to functionally augment the capabilities of our bare-bones shells and provide them with features that make them significantly easier to use.
To provide our shells with the extra capabilities, one approach is to directly edit our shell configuration files and add custom utility functions. For example, you can insert aliases or functions into your shell configuration file, and they become permanent additions to your shell environment.
foo() { echo "This is the foo function"}
The previous example will give you a reusable utility function called “foo” that will print only a basic message; you can use it like a command. However, while editing your shell is commonplace and even necessary for personal custom tools, it is a very manual approach and potentially more work than you’re willing to perform in the long term.
Another (and often better) approach is to use plugins. Plugins are simply packages made up of shell functions, written, tested, and published by someone else, so all the hard work is already done for you. To install plugins, you would typically use a shell framework—which is itself made up of shell functions. A shell framework will initialize your shell environment, facilitate the installation and activation of plugins, and often provide additional features.
Zsh was the first shell to establish a proper plugin ecosystem with a framework known as Oh My Zsh. After many years, the Zsh ecosystem has matured to the point where there are thousands of plugins, themes, and frameworks. Many people often recommend Oh My Zsh, but I prefer Zinit for its focus on a rapid startup time, which is mere milliseconds.
Whatever framework you choose, there is an abundance of Zsh plugins to select from, all of which solve a problem that someone once had. Spending a little time browsing for plugins can lead to significant long-term efficiency gains, making it a worthwhile investment. Instead of browsing YouTube, I often browse for plugins, trading procrastination for productivity gains.
Smart command assistance with first-class completions
As previously mentioned, typing a long command only to have the shell return an error is time-consuming and frustrating. We need guardrails to keep our clumsy human actions on track, and that’s exactly what shell completions do. When you type a command and hit the Tab key, the shell will list all the options available.
Bash does provide shell completions, but the selection process is stubborn, often requiring me to press the tab key 10 to 15 times to complete a single option. While Zsh’s completions are better by default, plugins and frameworks significantly augment their capabilities.
The previous images show Zsh completions augmented with a plugin that leverages the fzf utility. Fzf is an indispensable utility that allows you to narrow down your selections in real time using approximate search. When used with completions, your input doesn’t need to be entirely correct, and fzf will still help you find the relevant option. That’s helpful when you’re typing dozens of commands in quick succession, and you don’t take the time to be 100% correct with spelling.
Constructing commands is purely practical, and it’s not a work of art, so shell features should be geared toward making it as efficient as possible. Zsh does a superb job of that through excellent command completion support. I could not use a shell for very long if its completion system isn’t on par with Zsh’s offering.
Intelligent command-line editing with the Zsh line editor
The command line isn’t just about inputting commands; it’s often about editing them too. Both Zsh and Bash have competent command-line editors that provide advanced controls to manipulate the command text before entering it.
Bash uses GNU Readline, a general command-line editor used by many different software projects. It allows you to edit the command line using Emacs or Vi key bindings, making editing commands feel more like using a text editor. Readline is configurable, but it’s not extensible without writing custom C code. The Zsh Line Editor (ZLE) takes a different approach because it’s extensible via shell scripts. ZLE’s extensibility enables powerful community-driven features like syntax highlighting and custom widgets, in addition to supporting Emacs- and Vi-style key bindings.
Widgets (in my opinion) are where the ZLE truly shines. A widget is just a shell function that’s typically activated via keyboard shortcut. Widgets have access to the command line text and allow you to execute any shell command upon it before accepting the command. This opens a world of opportunities to make your workflow a little less painful.
My two favorite widgets make Git (a source code tracker) changes and choosing files less cumbersome.
Git widget
I frequently use Git to track and review source code changes. My first widget simplifies the commit command. Making Git commits is a frequent task, and typing out the entire command every time, including a descriptive message, gets tiring:
git commit --all --message "make lots of bugs"
After typing “git commit –all –message” a dozen times, the repetition starts to wear me down. That’s why I use the following widget.
git_commit_with_message() { local msg=$BUFFER BUFFER="git commit --all --message \"${msg}\"" zle accept-line}zle -N git_commit_with_messagebindkey "\e^M" git_commit_with_message
I place the previous shell function into my .zshrc and reload it. I can then make a Git commit by typing a descriptive message and hitting Alt+Enter. The widget then wraps it in a “git commit –all –message” command for me. This saves me a lot of energy in the long term.
Path picker widget
The second widget picks a file system path using a file manager instead of typing it out. Typing out long file paths is tedious, especially when navigating deep directory structures.
yazi_pick_path() { local file=$(yazi --chooser-file=>(cat)) [[ -z "$file" ]] && zle redisplay && return LBUFFER+="\"$file\"" zle redisplay}zle -N yazi_pick_pathbindkey '\ey' yazi_pick_path
Now when I type a command and I need a path, I can hit Alt+Y, and a Yazi file manager window will open in the terminal. When I select a file or directory and hit enter, Yazi will insert a quoted path into the command line for me.
The ZLE is powerful
These widgets allow me to remove the laborious elements from entering commands. The ZLE system is powerful, and with a little shell scripting knowledge, you can make difficult commands much easier to type. It’s one of those features that makes Zsh feel truly extensible.
While Bash does have popular frameworks (Bash-it and Oh My Bash) that come with some useful plugins, I don’t think their offerings compare to the Zsh ecosystem. In addition, Zsh also offers much more advanced completion and line editing systems. These three powerful systems allow me to leverage other people’s work and customize my environment in a way that I find difficult to do with Bash.