Bash Prompt #1: Colours

Bash Prompt Index

This isn't where I wanted to start covering creating Bash Prompts, but the script I really wanted to start with requires colours, and colours in prompts are complicated.

Here are the colour codes for the 16 colours that are supported in essentially all Bash terminals:

ColourCode Bold ColourCode
Black0;30 Dark Gray1;30
Blue0;34 Light Blue1;34
Green0;32 Light Green1;32
Cyan0;36 Light Cyan1;36
Red0;31 Light Red1;31
Purple0;35 Light Purple1;35
Brown0;33 Yellow1;33
Light Gray0;37 White1;37

Handling of colours varies between terminals, so my names are at best an approximation. If you're on a Mac, you'll find it tends to bold with the 1; preface, while on Linux that usually leads to brightening the colour.

Most modern Linux terminals can handle 256 colours, but I don't think the Linux console does (that's when you're booted to a text console on a Linux machine, no graphics - yes, people still do that). And Debian may still be defaulting to 16 colour terminals. For this article, I'm going to stick to 16 colours - it's complicated enough. I'll cover 256 colours another day.

Here's basic Bash script usage (using colours in prompts is more complicated, we'll get to that):

$ echo -e "\e[1;35mBash Prompts\e[0;0m"

That's a bit more than just the codes I named in the table: the -e parameter to echo tells it to enable interpretation of backslash escape sequences. Escape characters start with \e (you'll also see \033 used occasionally). There are several escape characters: colours are prefaced by the open square bracket.

The problem with using these colours in a prompt (as opposed to just using them at the command line) is that they're non-printing characters ... but the Bash command line sees them as taking up space, with the end result that your command line wraps in the wrong place as you type. This is worse than it sounds: it makes it impossible to edit the command line as characters disappear and you can't move the cursor back to the previous part of the command line text. To fix this problem, we surround non-printing characters in the prompt with \[ and \]:

$ export PS1="\[\e[1;35m\]bashprompt\[\e[0;0m\]$ "

This will get you a purple prompt with a gray dollar sign on the end. If you'd like to see the wrap problems I was talking about, just remove the escaped square brackets around the colour codes and try it again. This will look exactly the same. To see the problem, hold down any key until it repeats its way to the edge of the terminal - except it won't get there. It will wrap early and badly, because the prompt has counted the wrong number of characters in the ${PS1} string because the colour codes weren't escaped.

Background colours are started and ended the same way:

$ export PS1="\[\e[45m\]bashprompt\[\e[0;0m\]$ "

There are a couple differences: the code starts with a "4" rather than a "3" and there are no bold/bright background colours so you have only eight choices. In this case, you'll get default gray text on a purple background. Finally, you can have a mix of colour background and foreground:

$ export PS1="\[\e[43m\]bash\[\e[1;31m\]prompt\[\e[0;0m\]$ "

Here's a script that will show you the 16 colours and the eight background colours in every combination:

#!/bin/bash
#
#   This file echoes a bunch of colour codes to the terminal to demonstrate
#   what's available.  Each line is the colour code of one forground
#   colour, out of 17 (default + 16 escapes), followed by a test use of
#   that colour on all nine background colours (default + 8 escapes).
#

T='gYw'   # The test text

echo
echo "              40m   41m   42m   43m   44m   45m   46m   47m"

for FGs in '   0m' ' 1;0m' '  30m' '1;30m' '  31m' '1;31m' '  32m' \
           '1;32m' '  33m' '1;33m' '  34m' '1;34m' '  35m' '1;35m' \
           '  36m' '1;36m' '  37m' '1;37m'
    do
        FG=${FGs// /}
        echo -en " ${FGs} \033[${FG} ${T} "
        for BG in 40m 41m 42m 43m 44m 45m 46m 47m
        do
            echo -en " \033[${FG}\033[${BG} ${T} \033[0m"
        done
    echo
    done
echo

For various reasons I don't fully remember anymore (abstraction problems mentioned in the next script, and cross-platform compatibility with the Mac) I've ended up using different escape sequences that seem to work better and more widely. Here's the script I use to load a set of colours for the prompt. You can of course just use the bits you need, and rename the colours to saner values. It also includes background colours and some other escape sequences (italics, blink, etc.) that I haven't covered here.

# Colours for prompts:
#    _pc_color          foreground colour
#    _pc_b_color bright foreground colour
#    _pc_bg_color       background colour
#
# NOTE: to combine a BG and FG, list the FG FIRST, then the BG colour.
#
# Colours without the prompt escape sequences:
#    _c_color           foreground colour
#    _c_b_color  bright foreground colour
#    _c_bg_color        background colour
#
# NOTE:
# Colours of the form "\[\e[38;5;015m\]" work fine in many contexts, and
# I've used them for years.  But if you attempt to pass them as parameters
# ... I haven't found any context or escape sequence to make that work.
# Instead, we use '\001[\033[38;5;015m\002'.

export _pc_nocolour='\001\033[0;0m\002'
export _pc_nocolor='\001\033[0;0m\002' # for our American friends
export _c_nocolour='\033[0m'
export _c_nocolor='\033[0m'

export _pc_bold='\001$(tput bold)\002'
export _pc_underline='\001$(tput smul)\002'
# italics display is very terminal-dependent: on some, it's reverse video.
export _pc_italics='\001$(tput sitm)\002'
export _pc_reverse='\001$(tput rev)\002'
export _pc_blink='\001$(tput blink)\002'

export _pc_black='\001\033[38;5;000m\002'
export _pc_b_black='\001\033[38;5;008m\002' # dark gray
export _pc_darkgray='\001\033[38;5;008m\002'
export _pc_bg_black='\001\033[48;5;000m\002'
export _c_black='\033[38;5;000m'
export _c_b_black='\033[38;5;008m'
export _c_darkgray='\033[38;5;008m'
export _c_bg_black='\033[48;5;000m'

export _pc_red='\001\033[38;5;001m\002'
export _pc_b_red='\001\033[38;5;009m\002'
export _pc_bg_red='\001\033[48;5;001m\002'
export _c_red='\033[38;5;001m'
export _c_b_red='\033[38;5;009m'
export _c_bg_red='\033[48;5;001m'

export _pc_green='\001\033[38;5;002m\002'
export _pc_b_green='\001\033[38;5;010m\002'
export _pc_bg_green='\001\033[48;5;002m\002'
export _c_green='\033[38;5;002m'
export _c_b_green='\033[38;5;010m'
export _c_bg_green='\033[48;5;002m'

export _pc_yellow='\001\033[38;5;003m\002'
export _pc_b_yellow='\001\033[38;5;011m\002'
export _pc_bg_yellow='\001\033[48;5;003m\002'
export _c_yellow='\033[38;5;003m'
export _c_b_yellow='\033[38;5;011m'
export _c_bg_yellow='\033[48;5;003m'

export _pc_blue='\001\033[38;5;004m\002'
export _pc_b_blue='\001\033[38;5;012m\002'
export _pc_bg_blue='\001\033[48;5;004m\002'
export _c_blue='\033[38;5;004m'
export _c_b_blue='\033[38;5;012m'
export _c_bg_blue='\033[48;5;004m'

export _pc_purple='\001\033[38;5;005m\002'
export _pc_b_purple='\001\033[38;5;013m\002'
export _pc_bg_purple='\001\033[48;5;005m\002'
export _c_purple='\033[38;5;005m'
export _c_b_purple='\033[38;5;013m'
export _c_bg_purple='\033[48;5;005m'

export _pc_cyan='\001\033[38;5;006m\002'
export _pc_b_cyan='\001\033[38;5;014m\002'
export _pc_bg_cyan='\001\033[48;5;006m\002'
export _c_cyan='\033[38;5;006m'
export _c_b_cyan='\033[38;5;014m'
export _c_bg_cyan='\033[48;5;006m'

export _pc_gray='\001\033[38;5;007m\002'
export _pc_b_gray='\001\033[38;5;015m\002' # almost white
export _pc_bg_gray='\001\033[48;5;007m\002'
export _c_gray='\033[38;5;007m'
export _c_b_gray='\033[38;5;015m'
export _c_bg_gray='\033[48;5;007m'