Bash shell prompt dissected: Display Git branch name and status in color

I received a snippet of code from my good friend Jason today. It goes into your .bash_profile and what it does is add two pieces of information to your Bash shell prompt when you’re in a Git working directory. With it, you’ll be able to tell:

  1. Which branch you’re currently on and
  2. Whether the branch has untracked changes.

The code, now compacted, looks like this:

1
2
3
export PS1='\[email protected]\h:\[\033[1;34m\]\w\[\033[0;33m\]$(git branch 2> /dev/null | sed 
-e '/^[^*]/d' -e "s/* \(.*\)/[\1$([[ $(git status 2> /dev/null | tail -n1) != 
"nothing to commit, working directory clean" ]] && echo "*")]/")\[\e[0m\]$ '
export PS1='\[email protected]\h:\[\033[1;34m\]\w\[\033[0;33m\]$(git branch 2> /dev/null | sed 
-e '/^[^*]/d' -e "s/* \(.*\)/[\1$([[ $(git status 2> /dev/null | tail -n1) != 
"nothing to commit, working directory clean" ]] && echo "*")]/")\[\e[0m\]$ '

Note (added on 5/19/14): I added two manual line breaks to the snippet above for readability, so if you are copying and pasting it, make sure everything ends up on one line. You can also, as Henry suggests, add a line continuation character to the end of the first two lines.

Holy Halloween, Batman!” is what I thought when I initially saw this, but after a good bit of research, it all made sense. I set off on a mission to be able to explain every character in this one-liner. Hopefully, after you read this, you’ll be able to modify your prompt to meet your needs and feel comfortable creating your own variation of it.

export

The export command allows environment variables to be modified.

When a program is invoked, it is given an array of strings called the environment. This is a list of namevalue pairs, of the form name=value.

The shell provides several ways to manipulate the environment. On invocation, the shell scans its own environment and creates a parameter for each name found, automatically marking it for export to child processes. Executed commands inherit the environment. The export and declare -x commands allow parameters and functions to be added to and deleted from the environment.

If you run the printenv command, you can see all the variables that are currently loaded (I removed a few variables):

1
2
3
4
5
6
7
8
9
10
ryansechrest@home:~ $ printenv
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
TERM=xterm-256color
USER=ryansechrest
PWD=/Users/ryansechrest
LANG=en_US.UTF-8
PS1=\u@\h:\w\$
HOME=/Users/ryansechrest
...
[email protected]:~ $ printenv
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
TERM=xterm-256color
USER=ryansechrest
PWD=/Users/ryansechrest
LANG=en_US.UTF-8
PS1=\[email protected]\h:\w\$
HOME=/Users/ryansechrest
...

PS1

As you can see from above, one of the environment variables is PS1, which stands for “Prompt Statement 1,” not to be confused with the PlayStation 1. There are a total of four prompt statements:

  1. Default interaction prompt (PS1)— When you open Terminal.
  2. Continuation interaction prompt (PS2)— When your command spans multiple lines.
  3. Select prompt (PS3)— When you use select to build a menu.
  4. Debug prompt (PS4)— When you use set -x to debug output.

Default interaction prompt

# export PS1='\[email protected]\h \w$ '

[email protected] ~$

Continuation interaction prompt

# export PS2='continue> '

[email protected] ~$ echo 'Who knew multiple lines
continue> could be so much fun?'
Who knew multiple lines
could be so much fun?

Select prompt

# export PS3='Take: '

[email protected] ~$ ready
This is your last chance. After this, there is no turning back. You take the blue
pill - the story ends, you wake up in your bed and believe whatever you want to
believe. You take the red pill - you stay in Wonderland and I show you how deep the
rabbit-hole goes.

1) Blue
2) Red

Take: 2

The pill you took is part of a trace program. It's designed to disrupt your 
input/output carrier signal so we can pinpoint your location.

Debug prompt

# export PS4='Debug (\@) $ '

+ export 'PS1=\[email protected]\h \w$ '
+ PS1='\[email protected]\h \w$ '
+ export 'PS2=continue > '
+ PS2='continue > '
+ export 'PS3=Take: '
+ PS3='Take: '
+ export 'PS4=Debug (\@) $ '
+ PS4='Debug (\@) $ '
Debug (03:41 PM) $ alias 'll=ls -aGhlp'
Debug (03:41 PM) $ alias '~=cd ~'
Debug (03:41 PM) $ alias '..=cd ../'
...
[email protected] ~$

PROMPT_COMMAND

There’s also a variable called PROMPT_COMMAND, which is executed right before the prompt statement is displayed. You could use this, for example, to modify the prompt or display additional information above it:

# export PROMPT_COMMAND=date

Sun Nov 17 15:45:39 CST 2013
[email protected] ~$

I executed the date command prior to displaying the prompt, which, as you can see, printed the date and time information right above the prompt.

All that said, if we wrap the PS1 value in single quotes, our new prompt starts like this:

1
export PS1=''
export PS1=''

If you are a less-is-more kind of person, this prompt is for you, but if the possibilities intrigue you, read on.

\[email protected]\h:

The backslash you see is the Bash escape character, which tells the shell to interpret the next character literally. If you’ve ever written anything in PHP, for example, I’m sure you’ve used literals to output newlines (\n), returns (\r), tabs (\t), and more.

I was just recently reminded that you have to remove newline breaks when processing contact form messages, otherwise the Agent Smiths of the world will exploit your contact form to spam the human race.

If you look at a table of special characters in Bash, you’ll see that:

  • \u is the username e.g. ryansechrest
  • \h is the hostname e.g. home

Here are all the characters that can appear in a prompt variable:

  • \a— A bell character.
  • \d— The date, in “Weekday Month Date” format (e.g., “Tue May 26”).
  • \e— An escape character.
  • \h— The hostname, up to the first ‘.’.
  • \H— The hostname.
  • \j— The number of jobs currently managed by the shell.
  • \l— The basename of the shell’s terminal device name.
  • \n— A newline.
  • \r— A carriage return.
  • \s— The name of the shell, the basename of $0 (the portion following the final slash).
  • \t— The time, in 24-hour HH:MM:SS format.
  • \T— The time, in 12-hour HH:MM:SS format.
  • \@— The time, in 12-hour am/pm format.
  • \A— The time, in 24-hour HH:MM format.
  • \u— The username of the current user.
  • \v— The version of Bash (e.g., 2.00).
  • \V— The release of Bash, version + patchlevel (e.g., 2.00.0).
  • \w— The current working directory, with $HOME abbreviated with a tilde.
  • \W— The basename of $PWD, with $HOME abbreviated with a tilde.
  • \!— The history number of this command.
  • \#— The command number of this command.
  • \$— If the effective uid is 0, #, otherwise $.
  • \nnn— The character whose ASCII code is the octal value nnn.
  • \\— A backslash.
  • \[— Begin a sequence of non-printing characters. This could be used to embed a terminal control sequence into the prompt.
  • \]— End a sequence of non-printing characters.

The at sign (@) and the colon (:) are printed out as-is. If you set PS1 to \[email protected]\h:, it would look like this:

1
ryansechrest@home:

At this point, PS1 looks like this:

1
export PS1='\[email protected]\h:'
export PS1='\[email protected]\h:'

\[\033[ ___ m\]

As you’ve just seen in my list, a backslash followed by an open (\[) and close (\]) square bracket marks the beginning and end, respectively, of a sequence of non-printing characters.

When you see a backslash and three digits (\033), it will be read as an eight-bit octal value, which is ASCII’s ESC character, presumably for the upcoming color code, 1;34.

The letter m tells Bash that it’s the end of the color sequence.

At this point, PS1 looks like this:

1
export PS1='\[email protected]\h:\[\033[ ___ m\]'
export PS1='\[email protected]\h:\[\033[ ___ m\]'

1;34

To set the color, you can supply multiple values separated by a semi-colon. Common values are found between 0 and 47, and can be grouped into three categories:

  1. 0-8— Text attributes
  2. 30-37— Foreground colors
  3. 40-47— Background colors

Here is a list of them:

Text attributes

  • 0— All attributes off
  • 1— Bold on
  • 4— Underscore (on monochrome display adapter only)
  • 5— Blink on
  • 7— Reverse video on
  • 8— Concealed on

Foreground colors

  • 30— Black
  • 31— Red
  • 32— Green
  • 33— Yellow
  • 34— Blue
  • 35— Magenta
  • 36— Cyan
  • 37— White

Background colors

  • 40— Black
  • 41— Red
  • 42— Green
  • 43— Yellow
  • 44— Blue
  • 45— Magenta
  • 46— Cyan
  • 47— White

For even more escape sequences, take a look at the ANSI escape sequence table.

If we put all that together, we get export PS1='\[email protected]\h:\[\033[1;34m\], which looks like this:

1
ryansechrest@home:

That’s right, it looks exactly the same as before, however, any characters I now type into the shell will be a bold and blue:

Image 1

Image 1

At this point, PS1 looks like this:

1
export PS1='\[email protected]\h:\[\033[1;34m\]'
export PS1='\[email protected]\h:\[\033[1;34m\]'

\w

Adding \w will output my current working directory:

Image 2

Image 2

It looks messy now, because once the color has been set (and not unset), everything will be in that color going forward.

At this point, PS1 looks like this:

1
export PS1='\[email protected]\h:\[\033[1;34m\]\w'
export PS1='\[email protected]\h:\[\033[1;34m\]\w'

\[\033[0;33m\]

You can probably deduce this now. It will turn all text attributes off (0) and change the color to yellow (0;33).

At this point, PS1 looks like this:

1
export PS1='\[email protected]\h:\[\033[1;34m\]\w\[\033[0;33m\]'
export PS1='\[email protected]\h:\[\033[1;34m\]\w\[\033[0;33m\]'

3 thoughts on “Bash shell prompt dissected: Display Git branch name and status in color

  1. Henry

    Great tip. Only thing I had to add were Unix line continuation characters (‘\’) at end of first two lines. Minor point, but added another couple minutes to deploy. Would be great for the next person if they were included in your snippet.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *