github linkedin vsco
← home

Customizing Shell Prompts

22 May 2024

Recently I went down the rabbit hole that is custom shell prompts. I had done this once before having tried Pure and Powerlevel10k. However, due to some issues with these prompts displaying in certain terminal emulators, I moved away from using an installed shell prompt and returned to the default zsh prompt.

Then, while working on one of our servers at work, I noticed the shell was using a custom prompt. Looking in the .bashrc I was surprised to see only the following few lines:

parse_git_branch() {
  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
export PS1='\e[0;32m[\u@\h \e[1;33m\w\e[0;32m \e[0;91m$(parse_git_branch)\e[0;32m]\e[0m\n$'

Again, I went down the rabbit hole of custom shell prompts, but, this time, I was interested in crafting my own custom prompt instead of installing a premade script or program that is too large to comprehend.

The first step in this process was understanding that line I found. The bash docs explain the special characters it used.

# .bashrc

PROMPT_COMMAND="\u@\h \W \$"

In bash, this line will produce the following prompt:

sam@macbook ~ $

However, I use zsh, so I referred to the zsh docs on prompt expansion. In zsh, the following line will produce the same prompt as above:

# .zshrc

precmd() { "n@%m %1~ \$" }

NOTE: precmd is a zsh hook that executes before the prompt is displayed.

One of the most beneficial parts of a custom prompt is the ability to display the git branch and status. Even the simple prompt from the server had this. I found similar solutions using custom functions as well as solutions using the vcs_info function in zsh. Ultimately, using Git’s official prompt script as an alternative to the vsc_info function and to maximize portability, as the git docs describe, is the implementation I chose.

# .zshrc

source $HOME/.git-prompt.sh
export GIT_PS1_SHOWCOLORHINTS=true
export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_UNTRACKEDFILES=true
NEWLINE=$'\n'
precmd () { __git_ps1 "${NEWLINE}[%n@%m] %~" "${NEWLINE}%(?..%F{red})\$%f " " %s" }

These lines will produce the following prompt:

[sam@macbook] ~/dotfiles master
$

Let’s break down this code. First, the .git-prompt.sh script is sourced so we can access to its functionality. Then, the environment variables for the prompt are set. Then a variable containing a newline character is created. The precmd hook begins with the function __git_ps1 which takes two parameters, pre and post, and puts the git status between them. However, a third parameter can be specified which defines how the git status will appear. The first parameter passed is "${NEWLINE}[%n@%m] %~", which is similar to the simple zsh example above, except it inserts a newline before the prompt using the NEWLINE variable. The second parameter passed is "${NEWLINE}%(?..%F{red})\$%f " where %(?..%F{red}) is a conditional substring that changes the color of the following characters to red if the exit status is not 0. \$%f is the $ prompt character followed by the closing %f for the color formatting and then a space. The third parameter " %s" removes the parenthesis that the git prompt adds around the status by default. To achieve the same result in bash, use the following code:

# .bashrc

source $HOME/.git-prompt.sh
export GIT_PS1_SHOWCOLORHINTS=true
export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_UNTRACKEDFILES=true
PROMPT_COMMAND='__git_ps1 "\n[\u@\h] \w" "\n\$ " " %s"'