Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Add ANSI Colors to Your Shell Scripts Using echo, printf and heredocs

add-ansi-colors-to-your-shell-scripts-using-echo-printf-and-heredocs.jpg

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.

Quick Jump:

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”
  • [ starts a command
  • 0 resets all previous colors back to their default
  • ; is a delimiter for arguments which you’ll see repeated
  • 36 is the color code for cyan
  • 1m sets the mode, in this case 1 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))

NameNormal FGNormal BGBright FGBright BG
Black304090100
Red314191101
Green324292102
Yellow334393103
Blue344494104
Magenta354595105
Cyan364696106
White374797107

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:

ansi-color-output.jpg

# 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 bold
  • 2m dim
  • 3m italic
  • 4m underline
  • 5m blink
  • 7m inverse
  • 8m hidden
  • 9m strike through

Here’s what they look like with my set up using red:

ansi-color-modes.jpg

# 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.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per year (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments