DEV Community

David R. Myers
David R. Myers

Posted on • Edited on • Originally published at voracious.dev

A Guide to Customizing the Zsh Shell Prompt

The goal of this article is to teach you just enough about the shell prompt to make some helpful customizations.

What is the shell prompt?

The prompt is the bit of text that shows up in our shells to indicate that we can interact with them. The prompt usually gives us some details about the current shell session such as username, machine name, current directory, and some kind of prompt termination token. An example might look something like this.

david@macbook /tmp $ 
Enter fullscreen mode Exit fullscreen mode

All of this information can be customized through the shell's prompt strings. Each shell has specific escape sequences that must be used. This just means that we have to use different tokens to represent things such as username, color formatting, etc. For the purposes of this article, we will be using Zsh, and we can confirm that is our current shell by running this.

echo $0 # /bin/zsh
Enter fullscreen mode Exit fullscreen mode

Introducing PS1

The primary prompt string is stored in a variable called PS1. There are five prompt strings in total, so the trailing number denotes its responsibility. The primary prompt string is the one that is printed to stdout when the shell is waiting for a new command, and it is therefore the one we probably see most frequently. Unlike a typical variable, these prompt strings undergo expansion every time the prompt is displayed. This is what allows us to update the displayed path after changing directories, for example. A PS1 string might look something like this.

PS1='%n@%m %/ $ ' # david@macbook /tmp $ 
Enter fullscreen mode Exit fullscreen mode

We will dive into this more below.

Breaking it down

The first thing to note is the use of single quotes ''. We can use double quotes, but we have to remember to escape all of the expressions that we want to re-evaluate each time the prompt is displayed. Double quoted strings undergo normal expansion and substitution before being stored in a variable, so evaluation would only happen once per shell session rather than once per prompt. It doesn't make a difference for our simple example above, but it starts to matter when we introduce things like variables and shell expressions. More on that later.

Next, let's take a look at each of the escape sequences in our prompt string. The first one is %n, and it represents our username. The next escape sequence is %/, and it represents the current directory. For these examples, we will assume the username is david and the current directory is /tmp. For more on Zsh prompt expansion, check out the docs.

How do we customize it?

We are going to build our prompt from scratch, so let's start with some basic information. We talked about the PS1 variable above, but we didn't talk about where it needs to be defined. For Zsh, we will use ~/.zshrc to set the PS1 variable. This file is loaded at the beginning of every shell session, so it will ensure our configuration is respected for all sessions and not just the current one. Every shell has a default prompt string, and we can view the raw source by echoing the PS1 variable (e.g. echo $PS1). While we can build on top of the existing one, for the purposes of learning, we are going to build ours from scratch. Add the following to your Zsh config.

# ~/.zshrc
PS1='$ ' # $ 
Enter fullscreen mode Exit fullscreen mode

This prompt does its primary job of telling us the shell is ready for input, but we can make it much more useful. Let's start by adding our username %n, our machine name %m, and our current directory %/.

# ~/.zshrc
PS1='%n@%m %/ $ ' # david@macbook /tmp $ 
Enter fullscreen mode Exit fullscreen mode

That's looking a lot more like what we might expect from a typical shell prompt.

Adding some color

Now that we have a basic prompt, we can spice it up with some color. To change the color of our prompt, we need to use a new type of sequence. Many modern terminals support 256 colors, and while some of these can be referenced by name, most will need to be referenced by their color code - ranging from 0 to 255. Colors are applied via start and stop sequences indicated by %F{} and %f, respectively. Let's put this into practice by coloring our current directory red.

# ~/.zshrc
PS1='%n@%m %F{red}%/%f $ ' # david@macbook /tmp $ 
Enter fullscreen mode Exit fullscreen mode

Adding the current Git branch

Another useful piece of information which is not typically displayed by default is version control. Not all directories are version controlled, so this info will only show up when it is relevant. For this guide, we will use Git, but Zsh actually does support a few others. We are going to add the branch name of the current repo to our prompt, and in order to do this, we need to cover a few details about vcs_info . It is a framework that ships with Zsh to gather information from various version control systems.

# ~/.zshrc
autoload -Uz vcs_info # enable vcs_info
precmd () { vcs_info } # always load before displaying the prompt
Enter fullscreen mode Exit fullscreen mode

This block is what actually enables and autoloads vcs_info before each rendering of the shell prompt. It stores the data in a new variable called vcs_info_msg_0_. If we use this variable in our prompt string, it will display some basic info such as our version control system and the current branch (e.g. (git)-[main]-). To format this info, we can use zstyle like so.

# ~/.zshrc
zstyle ':vcs_info:*' formats ' %s(%b)' # git(main)
Enter fullscreen mode Exit fullscreen mode

The zstyle format string has its own tokens that are expanded in the prompt string. The first token %s represents the current version control system. The next token %b represents the current branch name. For more information on available tokens, check out the docs. Finally, we can combine everything we've learned and add a bit of color to the branch name.

# ~/.zshrc
autoload -Uz vcs_info # enable vcs_info
precmd () { vcs_info } # always load before displaying the prompt
zstyle ':vcs_info:*' formats ' %s(%F{red}%b%f)' # git(main)

PS1='%n@%m %F{red}%/%f$vcs_info_msg_0_ $ ' # david@macbook /tmp/repo (main) $ 
Enter fullscreen mode Exit fullscreen mode

Final result

To summarize, we have learned how to include specific information about our system and shell session, how to display version control status information, and how to apply color to these various pieces of information.

Closing

I hope this article helped shed some light on the shell prompt syntax. It was a lot of fun to write while experimenting with my own prompt (which you can see at the top of this page). As a final note, I would love to share octo - a hackable, offline-first markdown editor for notes, code snippets, and writing that runs entirely in-browser. It's free and open source, and working on it gives me a lot of ideas and inspiration for the articles I write. Thank you, and happy coding. ✌️

Top comments (5)

Collapse
 
synchromesh profile image
John Pallister

Thanks David, one question: what actually is the prompt format string you came up with for your own use? Cheers!

Collapse
 
voracious profile image
David R. Myers

Hey, thanks for reading through this article! I have adapted it since writing this article, but here is my current prompt string configuration (including the necessary bits for git status indicators).

parse_git_dirty() {
  git_status="$(git status 2> /dev/null)"
  [[ "$git_status" =~ "Changes to be committed:" ]] && echo -n "%F{green}·%f"
  [[ "$git_status" =~ "Changes not staged for commit:" ]] && echo -n "%F{yellow}·%f"
  [[ "$git_status" =~ "Untracked files:" ]] && echo -n "%F{red}·%f"
}

setopt prompt_subst

NEWLINE=$'\n'

autoload -Uz vcs_info # enable vcs_info
precmd () { vcs_info } # always load before displaying the prompt
zstyle ':vcs_info:git*' formats ' ↣ (%F{254}%b%F{245})' # format $vcs_info_msg_0_

PS1='%F{254}%n%F{245} ↣ %F{153}%(5~|%-1~/⋯/%3~|%4~)%F{245}${vcs_info_msg_0_} $(parse_git_dirty)$NEWLINE%F{254}$%f '
Enter fullscreen mode Exit fullscreen mode

This is what it looks like.

Enjoy!

Collapse
 
thangphan_devops profile image
Thang Phan

Thank you so much, this will help my terminal run faster and much more simple :)

Collapse
 
drmfree profile image
Anaël Leinert

As of today, this guide is missing this line:
setopt prompt_subst
Otherwise, vcs_info_msg_0_ will not be estimated.

Collapse
 
voracious profile image
David R. Myers

If you found this article useful or you found it lacking, please let me know! I'd love to improve my writing and improve this resource for you all. I'm just a quick comment away. 😁