Customizing Shell Prompts
22 May 2024Recently 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"'