Add ANSI Colors to Your Shell Scripts Using echo, printf and heredocs
We'll go over how to set ANSI colors as well as reset them so you can add assorted colors and styles to your scripts.
Sometimes it’s nice to bring a little life to your scripts. Using ANSI color codes is one way to do that. Color support in modern terminals is quite good nowadays. You’ll typically find 8, 16 and 256 color support as well as 24-bit true colors (RGB) being available.
For example, here’s how to output cyan text: printf "\e[0;36;1mhello in cyan\e[0m\n"
In the above example:
\e
is the escape code- In other scripts you may have seen
\033
or\x1B
used, they’re all comparable and are POSIX compliant, in my own scripts I reach for\e
and think of “e” as “escape”
- In other scripts you may have seen
[
starts a command0
resets all previous colors back to their default;
is a delimiter for arguments which you’ll see repeated36
is the color code for cyan1m
sets the mode, in this case1
is bold\e[0m
resets everything back to the default styling- This is a nice thing to do in case future output doesn’t do this
- Some shells like zsh will do this automatically
Let’s focus on colors. This gist has a more detailed explanation of ANSI escape codes.
# Colors
I find myself Googling for these things from time to time so I’ve centralized a few pieces of information from different sources.
16 Colors (Normal (8) + High Intensity (8))
Name | Normal FG | Normal BG | Bright FG | Bright BG |
---|---|---|---|---|
Black | 30 | 40 | 90 | 100 |
Red | 31 | 41 | 91 | 101 |
Green | 32 | 42 | 92 | 102 |
Yellow | 33 | 43 | 93 | 103 |
Blue | 34 | 44 | 94 | 104 |
Magenta | 35 | 45 | 95 | 105 |
Cyan | 36 | 46 | 96 | 106 |
White | 37 | 47 | 97 | 107 |
256 Colors
You can copy / paste this in your terminal to see the results, but the main
takeaway here is [38;5;${i}m
. 38
is for setting the foreground color and
48
is for the background.
# Source: https://unix.stackexchange.com/a/308095
for i in {0..255}; do
printf "\e[38;5;${i}m%3d " "${i}"
if ((i == 15)) || ((i > 15)) && (((i - 15) % 12 == 0)); then
echo
fi
done
24-bit RGB Colors
You can copy / paste this in your terminal to see the results, but the main
takeaway here is \e[38;2;{r};{g};{b}m
defines the pattern. Like 256 colors
you can switch to 48
instead of 38
for background vs foreground colors.
# Source: https://unix.stackexchange.com/a/404415
awk -v term_cols="${width:-$(tput cols || echo 80)}" 'BEGIN{
s="/\\";
for (colnum = 0; colnum<term_cols; colnum++) {
r = 255-(colnum*255/term_cols);
g = (colnum*510/term_cols);
b = (colnum*255/term_cols);
if (g>255) g = 510-g;
printf "\033[48;2;%d;%d;%dm", r,g,b;
printf "\033[38;2;%d;%d;%dm", 255-r,255-g,255-b;
printf "%s\033[0m", substr(s,colnum%2+1,1);
}
printf "\n";
}'
Most modern terminals support true color such as Microsoft Terminal, Ghostty, WezTerm, Kitty, Alacritty and more. This repo has more info about true color support.
Color Outputs
Here’s what it looks like with my set up:
# Color Modes
Here’s a few examples with different modes:
printf "\e[0;32;1mhello in normal green\n"
printf "\e[0;43;2mhello with dim yellow background\n"
Here’s a chart of different modes, your terminal / font might not support all of them:
1m
bold2m
dim3m
italic4m
underline5m
blink7m
inverse8m
hidden9m
strike through
Here’s what they look like with my set up using red:
# printf / echo / heredocs
Here’s a few examples on how to use colors with different ways of printing text:
# The most basic case.
printf "\e[0;36;1mhello in cyan\e[0m\n"
# Using a variable, we're using %b to interpret escaped backslashes instead of %s.
printf "%bhello in cyan\e[0m\n" "\e[0;36;1m"
# This won't work as expected in sh or bash, it will literally output what's in
# quotes. However, with zsh this will work as expected and display colored text.
echo "\e[0;36;1mhello in cyan\e[0m"
# A sh and bash compatible way to work more like printf.
echo -e "\e[0;36;1mhello in cyan\e[0m"
# The cat util doesn't interpret escaped characters so we wrap it in echo -e.
echo -e "$(
cat <<EOF
\e[0;36;1mhello in cyan\e[0m
EOF
)"
# Using Easier to Remember Names
In scripts that might be using colors in a few spots it could be helpful to define them in variables because typing those escape codes can get unwieldy.
Here’s a few snippets from my dotfile’s install script where I sprinkle in a little bit of color:
# You can name these whatever you want.
C_RED="\e[0;31;1m"
C_GREEN="\e[0;32;1m"
C_CYAN="\e[0;36;1m"
C_RESET="\e[0m"
_error() {
local message="${1:-}"
printf "%bERROR: %s%b\n\n" "${C_RED}" "${message}" "${C_RESET}" >&2
exit 1
}
_info() {
local message="${1:-}"
printf "\n\n%b%s:%b\n\n" "${C_CYAN}" "${message}" "${C_RESET}"
}
echo -e "$(
cat <<EOF
Usage examples:
# Pull in remote updates and run the script to update your dotfiles.
${C_GREEN}./install${C_RESET}
# ...
EOF
)"
The video below shows some of these colors.
# Demo Video
Timestamps
- 0:34 – Breaking down the format
- 1:32 – 8 foreground and background colors
- 2:31 – Styling bold, dim, strike through and more
- 3:39 – Resetting styles
- 4:37 – 256 colors
- 6:34 – 24-bit true RGB colors
- 8:25 – Circling back to modes
- 9:04 – printf with and without variables
- 10:54 – echo
- 11:56 – heredoc
- 13:03 – Using variables for colors in a script
- 14:02 – My dotfiles install script uses colors
How often are you adding color to your scripts? Let me know below.