A Clean Fast And Interactive Shell With Zsh Oh My Zsh And Starship

Intro

ZSH, Oh My ZSH, Starship full example

As programmers we typically spend a lot of time interacting with some kind of shell through a terminal (emulator) application. For obvious reasons a good terminal setup improves productivity and reduces stress. Typically, installing and configuring terminal and shell is one of the first things I do when setting up a new computer.

physical terminal To give a bit of background, a terminal (emulator) app is a graphical user interface which allows text-based interaction with the underlying operating system. This interaction is mediated by a shell which gives us programmatic access to the operating system’s services.

In the olden days, physical terminals (consoles) were used. Nowadays however, terminals are typically software-based, highly configurable and we have a lot of options.

:information_source: A nice write-up of the terminology can be found here.

If you’re a bit finicky like me, you want your terminal experience to be as

  • clean,
  • fast, and
  • interactive

as possible. Obviously, there are a lot of different ways to accomplish this, but the following setup is what I found works best for me.

Setup

Picking a terminal app

On macOS, there’s a couple of good choices for a terminal apps. Even the default Terminal.app isn’t half bad. Personally, I’m using iTerm2 which to my mind nicely integrates a couple of must-have features like * tabs, * panes, * session restoration, and * customization (e.g. fonts, themes, cursor, visual warnings instead of annoying beeps).

iTerm2 can be installed with brew

brew install --cask iterm2

We can install color themes in iTerm2 via the color preferences panel (CMD + I) by importing an .itermcolors file in Colors > Color Presets… > Import…. A list of themes can be found here.

Personally, I’m using the Andromeda theme.

iTerm2 with Andromeda theme

Another very nice configuration option is to silence the warning bell and instead having it show a visual bell symbol in case of errors. This option can be found in the Profiles > Terminal > Notifications section of the preferences (CMD + ,).

Picking a shell

Shell options on macOS. Now let’s move on to picking a shell. By default macOS used to ship with BASH as the default shell. This changed with macOS Catalina which switched to ZSH.

:information_source: Other pre-installed options include C Shell (CSH), Korn Shell (KSH), and Debian Almquist Shell (DASH).

BASH icon BASH is probably the most widely distributed POSIX-compatible shell. Consequently, if you want to use scripts written for BASH, backwards-compatibility is an important aspect when choosing an alternative shell.

That being said, BASH has some shortcomings particularly with respect to performance and extensibility. ZSH also has better spelling correction. For me ZSH is the perfect blend of modern features and backwards compatibility which is why I use it. Other shells should in principle also work with the following instructions, so feel free to pick your own shell flavor.

Installing ZSH. Although ZSH ships with macOS, it’s sensible to have the installation managed by brew (e.g. in order to get more frequent updates).

brew install zsh

To verify installation success, get the version

$ zsh --version
zsh 5.8 (x86_64-apple-darwin20.1.0)

We also need to inform macOS, that we want to use the brew managed installation of ZSH as our default shell from now on.

chsh -s $(which zsh)

Another nice thing about using ZSH is Oh My ZSH which manages your ZSH installation and configuration. Oh My ZSH can be installed using an install script

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Running the installation script will also add a template for configuration options (most of which are commented) to your ZSH configuration file in ~/.zshrc. Below are the relevant parts of my ~/.zshrc file minus the comments on my machine.

# ~/.zshrc
export ZSH="/Users/main/.oh-my-zsh"

source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

export ZSH_DISABLE_COMPFIX=true
source $ZSH/oh-my-zsh.sh

setopt interactivecomments # Activate bash style comments

The only strictly necessary parts are the export of the ZSH variable which points to the root of the ZSH installation and the source $ZSH/oh-my-zsh.sh line. Also compfix is disabled in line five because it clashes with the permissions in some directories managed by brew. setopt interactivecomments moreover lets us use comments in interactive mode which is sometimes helpful if we copy/paste commented code.

Syntax highlighting

I additionally added a line for initialisation of the syntax highlighting plugin in line three.

# ~/.zshrc
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

The syntax highlighting plugin provides on-the-fly syntax highlighting for BASH code passed to ZSH. We can install it using brew by running

brew install zsh-syntax-highlighting

Configuring the prompt

The command prompt is the part of the terminal UI that provides input for shell commands by the user. Typically, the prompt is preceded by context information printouts such as the active user or the working directory.

Oh My ZSH already provides an extension point to configure the prompt via themes. Since it’s entirely BASH-based, the performance however is not necessarily the best. A faster—and also highly configurable alternative—is Starship which is written in Rust.

To install Starship with brew, run the following command

brew install starship

Starship supports a wide varietey of shell flavors. To initialise it for ZSH, we have to add the following line to ~/.zshrc

# ~/.zshrc
eval "$(starship init zsh)"

Now to configure the prompt, we edit ~/.config/starship.toml. Below is the configuration that produces the prompt shown in the screenshot at the beginning of this post.

# starship.toml
format = """
[┌](bold) $time $username$hostname $git_branch
[└](bold) $directory
$character"""

[character]
success_symbol = "[➡](bold green)"
error_symbol = "[➡](bold red)"

[time]
disabled = false
format = '[$time](bold)'
style = "bold"
time_format = "%T"
utc_time_offset = "+2"

[username]
style_user = "#05bc79"
style_root = "bold #05bc79"
format = "[$user]($style)"
disabled = false
show_always = true

[hostname]
ssh_only = false
style = "#05bc79"
format = "[@$hostname]($style)"

[directory]
truncation_length = 10
truncate_to_repo = false
truncation_symbol = "…/"
read_only_style = "red"
style = ""
home_symbol = "~"

[git_branch]
always_show_remote = true
symbol = ""
truncation_length = 24
truncation_symbol = ""
style = "#0fa8cd"
format = "[$symbol](bold $style)[$branch]($style)"

The top level format property tells Starship how to draw the prompt particularly what context information to print. As an example configuration, consider the [username] block which draws the currently active user in the default green of the Andromeda theme palette. I tend to try to reduce the visual clutter, so I can parse the prompt faster. The only context information I need to see almost all the time are the * timestamp of the command execution, * user and host machine (to be able to find out if a SSH session is active) * the active Git branch, * working directory, and * last command exit status. Consequently, I leave every other component disabled.

The last aspect is handled nicely by starship by providing different formatting options for successful and unsuccessful commands in the [character] block.

Ligatures

To add some polishing, we will use a modern monospaced font with ligatures (e.g. to show instead of <=). My choice is Fira Code because it looks very clean and is available on brew via the cask-fonts cask. To install Fira Code run the following commands

brew tap homebrew/cask-fonts
brew install --cask font-fira-code

Remember to configure the font in your terminal application as well. In iTerm2 the setting is under Profiles > Text > Font.

Neofetch

Now, to top it all off, we add a nice system spec printout when a new terminal window, panel, or pane is opened. The printout is powered by neofetch which can also be installed by brew

brew install neofetch

To render the system spec printout, we simply add the neofetch command to the front of our ~/.zshrc

# ~/.zshrc
neofetch

Happy Hacking

That’s it, happy hacking! :)

Happy Hacking! :)