DEV Community

Cover image for From MUD Colours to Modern Terminals: A Bash Colour Library That Actually Makes Sense
gesslar
gesslar

Posted on • Edited on

From MUD Colours to Modern Terminals: A Bash Colour Library That Actually Makes Sense

Or: How I brought shell scripting from 1975 into 1995.

The Problem: ANSI Escape Codes Are Tedious AF

Let's be honest - working with terminal colours in bash is painful. You either memorise cryptic escape sequences like \033[38;5;196m or constantly look them up. Want to make something bright AND red? Hope you enjoy typing \033[1;38;5;196m
every time.

As someone who comes from MUD development, I am accustomed to simple colour tokens like %^RED%^ or %^RESET%^. Why couldn't shell scripts be that elegant?

How many have

  1. cheat sheets for commonly used ANSI sequences? or
  2. bookmarks to ANSI escape code??
  3. a litany of shell scripts to reference as previous examples on how to do a colour???

Just me? All right.

The Old Solution

So we came up with shortcuts to use colours because early on, we decided we collectively disliked the idea of typing bare ANSI sequences everywhere. And, as a solution, we decided on this norm:

# Colours for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Colour, reset everything to default.

echo -e "${RED}✗ Error: Something went wrong${NC}"
echo -e "${GREEN}✓ Success: Operation completed${NC}"
echo -e "${YELLOW}⚠ Warning: Check your input${NC}"
Enter fullscreen mode Exit fullscreen mode

Yes, this is perfectly fine. And I don't hate this pattern, and I think it's definitely a mostly portable way to achieve it. There are other ways, that are even more portable, but the above is fairly typical nowadays. At least, that's what I see most commonly. YMMV, YEMV, YEET.

Anyway, while I don't hate it, I don't love it.

The Solution: Meet colours

So I built a tiny bash library that makes terminal colours actually pleasant to use:

#!/bin/bash -i

. ~/bin/colours

echo "$(F 196)This is red text$(RST)"
echo "$(F 046)$(B 196)Green text on red background$(RST)"
echo "$(S BI)$(F 220)Bright italic yellow text$(RST)"
Enter fullscreen mode Exit fullscreen mode

That's it. Clean, readable, and you can actually remember the function names. And with frequent usage, as we do in the MUD community, you begin to remember the colour codes you prefer and/or frequently use! Way easier to build muscle memory in the brain. Or, what limited amount remains for us old-timer-MUDders.

The API: Short and Sweet

The whole library is just 5 functions:

  • F(n) - Set foreground colour (0-255)
  • B(n) - Set background colour (0-255)
  • S(codes) - Composably set styles (Bold, Italic, Underline, etc.)
  • U(codes) - Composably unset specific styles
  • RST() - Reset everything

Want bright italic underlined text? Just $(S BIU). Want to turn off just the italic? $(U I).

Is it portable?

lol, no. But I don't need portability for my scripts. Portable is definitely important for numerous use cases, but not in any of mine. Maybe someone will take it and make a portable version, or maybe there is already a portable version that is better that someone else wrote, I don't know.

The Secret Sauce Source: Dual Purpose Design

Here's the fun bit - colours works as both a library AND a colour picker.

As a library: Source it in your scripts for the API

$COLOURISER  # I set this in my .bashrc
echo "$(F 219)Pretty colours!$(RST)"
Enter fullscreen mode Exit fullscreen mode

As a colour picker: Run it directly to see all 256 colours with their numbers

$ colours
Enter fullscreen mode Exit fullscreen mode

Colours

No more guessing which shade of blue is colour 75 vs 81. Just run it, pick the number you want, done.

Real-World Usage

I use this in all my utility scripts now. Compare these two approaches:

Before (the bad old days):

echo -e "\033[32m✓ Success\033[0m"
echo -e "\033[31m✗ Failed\033[0m"
echo -e "\033[33m⚠ Warning\033[0m"
Enter fullscreen mode Exit fullscreen mode

After (living in the future):

echo "$(F 46)✓ Success$(RST)"
echo "$(F 196)✗ Failed$(RST)"
echo "$(F 220)⚠ Warning$(RST)"
Enter fullscreen mode Exit fullscreen mode

So much cleaner! And when I want to change the exact shade of green, I just run colours, pick a new number, and update one digit.

The MUD Heritage

This design comes from years of MUD development where colour codes need to be:

  1. Fast to type (you use them constantly)
  2. Easy to read (mixed with lots of text)
  3. Memorable (no looking up codes)

Turns out these same principles make shell scripting way more pleasant too.

Get It

Because life's too short for echo -e "\033[1;38;5;196;48;5;21m".


What's your favourite terminal colour? Mine's 38 - a lovely blue that makes shell messages feel less hostile.

Script

License

This script is released under the Unlicense because idgaf. Below is it, in all of its splendour and gloury. 🇨🇦

Behold

#!/bin/bash -i

set -euo pipefail

# colours - 256 Colour Terminal Display and Library Script
#
# Clean grid with actual colour numbers for use in other scripts
#
# Copyright: nope
# Warranty: heh
# License: The Unlicense (https://unlicense.org/)
# - With love from gesslar and Claude 🥰 - 2025
#
# Usage: colours                    - Display colour chart
#        source colours             - Load colour functions into shell
#        $COLOURISER                - Use via environment variable
#
# Functions provided when sourced:
#   RST()     - Reset all formatting
#   F(n)      - Set foreground colour (0-255)
#   B(n)      - Set background colour (0-255)
#   S(codes)  - Set styles (B=bright, I=italic, U=underline, L=blink, R=reverse, S=strikethrough)
#   U(codes)  - Unset styles
#
# Setup Instructions:
#
# 1. Create an environment variable in your main shell script (ex: .bashrc)
#    pointing to this file. Example:
#
#     bash-like: export COLOURISER=". ~/bin/colours"
#     fish-like: set -gx COLOURISER ". $(realpath ~/bin/colours)"
#
# 2. Execute this script at the top of your script to bring in the colour
#    functions. Example:
#
#     #!/bin/bash
#
#     $COLOURISER
#
#     echo "$(F 219)This is foreground colour 219$(RST)"
#     echo "$(B 196)This is bright red background$(RST)"
#     echo "$(F 046)$(B 196)Green text on red background$(RST)"
#
# 3. 💰💰💰💰💰💰💰

# #############################################################################
# Main colour functions
# #############################################################################

# Reset function ##############################################################
RST() { echo -ne '\033[0m'; }

# Foreground colour function ##################################################
F() { echo -ne "\033[38;5;${1}m"; }

# Background colour function ##################################################
B() { echo -ne "\033[48;5;${1}m"; }

# Style function that accepts multiple codes ##################################
S() {
  local codes="$1"
  local output=""

  for (( i=0; i<${#codes}; i++ )); do
    case "${codes:$i:1}" in
      B) output+='\033[1m' ;;    # Bright
      D) output+='\033[2m' ;;    # Dim
      I) output+='\033[3m' ;;    # Italic
      U) output+='\033[4m' ;;    # Underline
      L) output+='\033[5m' ;;    # Blink
      R) output+='\033[7m' ;;    # Reverse
      S) output+='\033[9m' ;;    # Strikethrough
    esac
  done

  echo -ne "$output"
}

# Unset/turn off specific styles ##############################################
U() {
  local codes="$1"
  local output=""

  for (( i=0; i<${#codes}; i++ )); do
    case "${codes:$i:1}" in
      B) output+='\033[22m' ;;   # Turn off bright/dim
      D) output+='\033[22m' ;;   # Turn off bright/dim
      I) output+='\033[23m' ;;   # Turn off italic
      U) output+='\033[24m' ;;   # Turn off underline
      L) output+='\033[25m' ;;   # Turn off blink
      R) output+='\033[27m' ;;   # Turn off reverse
      S) output+='\033[29m' ;;   # Turn off strikethrough
    esac
  done

  echo -ne "$output"
}

# #############################################################################
# Display the colour chart. Pick a colour, _any colour_!!!!
# #############################################################################

# Only show the chart if the script is run directly, not sourced
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  echo -e "                     $(F 227)-= $(F 198)High colour List $(F 227)=-$(RST)"
  echo

  # Rainbow colours (16-231) - 3 sections, 6 rows each, 12 colours per row
  for i in {0..2}; do
    for j in {0..5}; do
      for k in {0..11}; do
        colour=$((16 + i * (6 * 12) + j + k * 6))
        printf "$(F $colour)%03d$(RST)  " "$colour"
      done
      echo
    done
  done

  echo

  # Grayscale (232-255) - 2 rows, 12 colours each
  for i in {0..1}; do
    for j in {0..11}; do
      colour=$((232 + i * 12 + j))
      printf "$(F $colour)%03d$(RST)  " "$colour"
    done
    echo
  done

  echo

  # Base colours (0-15) - 2 rows, 8 colours each
  for i in {0..1}; do
    for j in {0..7}; do
      colour=$((i * 8 + j))
      printf "$(F $colour)%03d$(RST)  " "$colour"
    done
    echo
  done
fi
Enter fullscreen mode Exit fullscreen mode

Whee!

This was my first post ever, how did I do?

Top comments (0)