Outputting text in a terminal can result in a monochrome experience for the user. Adding color is easy, so here I provide a script that includes helper methods for outputting text, using defined colors, and handling the dark mode vs light mode terminal windows.
Introduction
The default text output to a terminal is monochromatic and doesn't provide a simple method to provide context. For instance, you may want an error to appear in red, success in green, or important info to be output in bold.
Adding color to your terminal output is straightforward and involves outputting the correct control characters before your text.
Terminal Colors
To output colored text, you need to printf
or echo -e
(the -e
to ensure control characters are interpreted) the control characters for the required color, then output your text, and then (to be tidy) reset the output back to defaults. The following table lists the codes:
Color | Foreground | Background |
Default | \033[39m | \033[49m |
Black | \033[30m | \033[40m |
Dark red | \033[31m | \033[41m |
Dark green | \033[32m | \033[42m |
Dark yellow (Orange-ish) | \033[33m | \033[43m |
Dark blue | \033[34m | \033[44m |
Dark magenta | \033[35m | \033[45m |
Dark cyan | \033[36m | \033[46m |
Light gray | \033[37m | \033[47m |
Dark gray | \033[90m | \033[100m |
Red | \033[91m | \033[101m |
Green | \033[92m | \033[101m |
Orange | \033[93m | \033[103m |
Blue | \033[94m | \033[104m |
Magenta | \033[95m | \033[105m |
Cyan | \033[96m | \033[106m |
White | \033[97m | \033[107m |
and the reset code is \033[0m
The format of the string for foreground color is:
"\033[" + "<0 or 1, meaning normal or bold>;" + "<color code> + "m"
and for background:
"\033[" + "<color code>" + "m"
These codes can be output together in order to change fore- and back-ground colors simultaneously.
Using the Code
A simple example of red text:
printf "\033[91mThis is red text\033[0m"
An example of red text on a white background:
printf "\033[91m\033[107mThis is red text on a white background\033[0m"
This is a little cumbersome so I've created some simple subroutines that provide the means to output text in a more civilised manner.
Helper Functions
The following helper functions allow you to do stuff like:
WriteLine "This is red text" "Red"
WriteLine "This is red text on a white background" "Red" "White"
Much easier.
useColor="true"
function Color () {
local foreground=$1
local background=$2
if [ "$foreground" == "" ]; then foreground="Default"; fi
if [ "$background" == "" ]; then background="$color_background"; fi
local colorString='\033['
case "$foreground" in
"Default") colorString='\033[0;39m';;
"Black" ) colorString='\033[0;30m';;
"DarkRed" ) colorString='\033[0;31m';;
"DarkGreen" ) colorString='\033[0;32m';;
"DarkYellow" ) colorString='\033[0;33m';;
"DarkBlue" ) colorString='\033[0;34m';;
"DarkMagenta" ) colorString='\033[0;35m';;
"DarkCyan" ) colorString='\033[0;36m';;
"Gray" ) colorString='\033[0;37m';;
"DarkGray" ) colorString='\033[1;90m';;
"Red" ) colorString='\033[1;91m';;
"Green" ) colorString='\033[1;92m';;
"Yellow" ) colorString='\033[1;93m';;
"Blue" ) colorString='\033[1;94m';;
"Magenta" ) colorString='\033[1;95m';;
"Cyan" ) colorString='\033[1;96m';;
"White" ) colorString='\033[1;97m';;
*) colorString='\033[0;39m';;
esac
case "$background" in
"Default" ) colorString="${colorString}\033[49m";;
"Black" ) colorString="${colorString}\033[40m";;
"DarkRed" ) colorString="${colorString}\033[41m";;
"DarkGreen" ) colorString="${colorString}\033[42m";;
"DarkYellow" ) colorString="${colorString}\033[43m";;
"DarkBlue" ) colorString="${colorString}\033[44m";;
"DarkMagenta" ) colorString="${colorString}\033[45m";;
"DarkCyan" ) colorString="${colorString}\033[46m";;
"Gray" ) colorString="${colorString}\033[47m";;
"DarkGray" ) colorString="${colorString}\033[100m";;
"Red" ) colorString="${colorString}\033[101m";;
"Green" ) colorString="${colorString}\033[102m";;
"Yellow" ) colorString="${colorString}\033[103m";;
"Blue" ) colorString="${colorString}\033[104m";;
"Magenta" ) colorString="${colorString}\033[105m";;
"Cyan" ) colorString="${colorString}\033[106m";;
"White" ) colorString="${colorString}\033[107m";;
*) colorString="${colorString}\033[49m";;
esac
echo "${colorString}"
}
function WriteLine () {
local resetColor='\033[0m'
local str=$1
local forecolor=$2
local backcolor=$3
if [ "$str" == "" ]; then
printf "\n"
return;
fi
if [ "$useColor" == "true" ]; then
local colorString=$(Color ${forecolor} ${backcolor})
printf "${colorString}%s${resetColor}\n" "${str}"
else
printf "%s\n" "${str}"
fi
}
function Write () {
local resetColor="\033[0m"
local forecolor=$1
local backcolor=$2
local str=$3
if [ "$str" == "" ]; then
return;
fi
if [ "$useColor" == "true" ]; then
local colorString=$(Color ${forecolor} ${backcolor})
printf "${colorString}%s${resetColor}" "${str}"
else
printf "%s" "$str"
fi
}
Handling Dark Mode and Light Mode
In many Linux and Unix distros the terminal is black background, white text. On macOS, the default is white background with black text. There are ways and means to test and guess what's happening but ultimately you'll need to test on your system and decide what works for you.
What I do is assume that if we're on a mac, then we can test directly, otherwise we assume white on black. I then also try to stick to default colors, and where I want a bit of color, I'll stick to some predefined colors I know will work well anywhere.
function getBackground () {
if [[ $OSTYPE == 'darwin'* ]]; then
osascript -e \
'tell application "Terminal"
get background color of selected tab of window 1
end tell'
else
echo "0,0,0"
fi
}
function isDarkMode () {
local bgColor=$(getBackground)
IFS=','; colors=($bgColor); IFS=' ';
if [ ${colors[0]} -lt 20000 ] && [ ${colors[1]} -lt 20000 ] && [ ${colors[2]} -lt 20000 ]; then
echo "true"
else
echo "false"
fi
}
Once we have a rough idea of what we're dealing with, I'll do:
darkmode=$(isDarkMode)
if [ "$darkmode" == "true" ]; then
color_primary="Blue"
color_mute="Gray"
color_info="Yellow"
color_success="Green"
color_warn="DarkYellow"
color_error="Red"
else
color_primary="DarkBlue"
color_mute="Gray"
color_info="Magenta"
color_success="DarkGreen"
color_warn="DarkYellow"
color_error="Red"
fi
and to use these:
WriteLine "Predefined colors on default background"
WriteLine
WriteLine "Default colored text" "Default"
WriteLine "Primary colored text" $color_primary
WriteLine "Mute colored text" $color_mute
WriteLine "Info colored text" $color_info
WriteLine "Success colored text" $color_success
WriteLine "Warning colored text" $color_warn
WriteLine "Error colored text" $color_error
WriteLine
WriteLine "Default color on predefined background"
WriteLine
WriteLine "Default colored background" "Default"
WriteLine "Primary colored background" "Default" $color_primary
WriteLine "Mute colored background" "Default" $color_mute
WriteLine "Info colored background" "Default" $color_info
WriteLine "Success colored background" "Default" $color_success
WriteLine "Warning colored background" "Default" $color_warn
WriteLine "Error colored background" "Default" $color_error
On a classic Linux terminal:
On a macOS terminal:
Things are a bit murky so let's add one more function that will provide a contrasting foreground on whatever background we choose.
function ContrastForeground () {
local color=$1
if [ "$color" == "" ]; then color="Default"; fi
if [ "$darkmode" == "true" ]; then
case "$color" in
"Default" ) echo "White";;
"Black" ) echo "White";;
"DarkRed" ) echo "White";;
"DarkGreen" ) echo "White";;
"DarkYellow" ) echo "White";;
"DarkBlue" ) echo "White";;
"DarkMagenta" ) echo "White";;
"DarkCyan" ) echo "White";;
"Gray" ) echo "Black";;
"DarkGray" ) echo "White";;
"Red" ) echo "White";;
"Green" ) echo "White";;
"Yellow" ) echo "Black";;
"Blue" ) echo "White";;
"Magenta" ) echo "White";;
"Cyan" ) echo "Black";;
"White" ) echo "Black";;
*) echo "White";;
esac
else
case "$color" in
"Default" ) echo "Black";;
"Black" ) echo "White";;
"DarkRed" ) echo "White";;
"DarkGreen" ) echo "White";;
"DarkYellow" ) echo "White";;
"DarkBlue" ) echo "White";;
"DarkMagenta" ) echo "White";;
"DarkCyan" ) echo "White";;
"Gray" ) echo "Black";;
"DarkGray" ) echo "White";;
"Red" ) echo "White";;
"Green" ) echo "Black";;
"Yellow" ) echo "Black";;
"Blue" ) echo "White";;
"Magenta" ) echo "White";;
"Cyan" ) echo "Black";;
"White" ) echo "Black";;
*) echo "White";;
esac
fi
echo "${colorString}"
}
Then, in the Color
subroutine, we can do:
function Color () {
local foreground=$1
local background=$2
if [ "$foreground" == "" ]; then foreground="Default"; fi
if [ "$background" == "" ]; then background="$color_background"; fi
if [ "$foreground" == "Contrast" ]; then
foreground=$(ContrastForeground ${background})
fi
...
and to make our text with background color a little more legible, we do:
WriteLine
WriteLine "Default contrasting color on predefined background"
WriteLine
WriteLine "Primary colored background" "Contrast" $color_primary
WriteLine "Mute colored background" "Contrast" $color_mute
WriteLine "Info colored background" "Contrast" $color_info
WriteLine "Success colored background" "Contrast" $color_success
WriteLine "Warning colored background" "Contrast" $color_warn
WriteLine "Error colored background" "Contrast" $color_error
The results on a Linux terminal:
...and on a macOS terminal: