Complex Color Patterns for Shell Prompts

Complex Color Patterns for Shell Prompts

Letter-level alternating colors for bash and zsh shells.

Posted on April 1, 2021

Tags:

It's My Birthday!

No, that's not an April Fool's joke, and yes, I've heard them all before, har, har, har 😑.

Anyway, I typically like to make a nice little blog post on my birthday. My previous birthday posts have included a post about giving up as a creator and that I was shutting down the blog (which is obvious to see at this point was an April Fool's joke).

Colored Shell Prompts?! With Emojis?!

Most of us devs know we can color our shell prompts with various colors, and in shell syntax it is some sort of ugly looking formatting string like \e[31m - that one is for the color red, which I'm sure you had memorized and knew right away, right? 😂

Well, I took a deep dive recently on the whole shell prompt formatting story recently, after creating my Full Stack Course "Bash Commands and Scripting - from Beginner to Expert":

Loading...

All that aside, what if I told you that you could style your prompt in an advanced fashion, to make it look something like this:

A rasta bash prompt

or this:

A star-spangled bash prompt

or, my current setup, like this:

A rainbow bash prompt.

The Repository

The repository is here. There you'll find all the details about the actual color implementation and the steps you need to take to get these cool colors to show up in your shell!

As far as I know, I'm the first person to ever have done this! (Or at least published it) 😎 I know why now - because bash and zsh syntax makes creating such color patterns a massive headache! 😅

I believe I've saved you that headache by creating a repository for the snippets and have also pasted the code snippets below.

The Code

The repository is here. There you'll find all the details about the actual color implementation and the steps you need to take to get these cool colors to show up in your shell! For the impatient, a working script for both bash and zsh shells is below, but I can't promise that these following snippets are the most up-to-date versions.

bash

function buildColorPrompt() {

    # I always like showing what directory I am in (special character "\w" in PS1) - store the equivalent in this 'directory' variable
    directory=$(pwd)

    # Modify these to whatever you'd like!
    PROMPT_TEXT="awesome-shell-prompt-colors@awesome-machine [$directory] "

    # Colors seperated by comma - acceptable values are:
    # black, white, red, green, yellow, blue, magenta, cyan, light gray, light red, light green, light yellow, light blue, light magenta, light cyan
    PROMPT_COLORS="red,white,blue"

    # Colors!
    BLACK="\e[30m"
    WHITE="\e[97m"
    RED="\e[31m"
    GREEN="\e[32m"
    YELLOW="\e[33m"
    BLUE="\e[34m"
    MAGENTA="\e[35m"
    CYAN="\e[36m"
    LIGHT_GRAY="\e[37m"
    DARK_GRAY="\e[90m"
    LIGHT_RED="\e[91m"
    LIGHT_GREEN="\e[92m"
    LIGHT_YELLOW="\e[93m"
    LIGHT_BLUE="\e[94m"
    LIGHT_MAGENTA="\e[95m"
    LIGHT_CYAN="\e[96m"

    # End formatting string
    END_FORMATTING="\[\e[0m\]"

    # split PROMPT_COLORS into array
    count=0
    IFS=','
    for x in $PROMPT_COLORS
    do
        colors_array[$count]=$x
        ((count=count+1))
    done
    unset IFS

    # break PROMPT_TEXT into character array
    letters=()
    for (( i=0 ; i < ${#PROMPT_TEXT} ; i++ )) {
        letters[$i]=${PROMPT_TEXT:$i:1}
    }

    # build prompt with colors
    color_index=0
    ps1='\['
    for (( i=0 ; i < ${#letters[@]} ; i++ )) {
        # Determine color in this giant case statement
        color="${colors_array[color_index]}"
        case $color in
            "black")
                COLOR=$BLACK
                ;;
            "red")
                COLOR=$RED
                ;;
            "green")
                COLOR=$GREEN
                ;;
            "yellow")
                COLOR=$YELLOW
                ;;
            "blue")
                COLOR=$BLUE
                ;;
            "magenta")
                COLOR=$MAGENTA
                ;;
            "cyan")
                COLOR=$CYAN
                ;;
            "light gray")
                COLOR=$LIGHT_GRAY
                ;;
            "dark gray")
                COLOR=$DARK_GRAY
                ;;
            "light red")
                COLOR=$LIGHT_RED
                ;;
            "light green")
                COLOR=$LIGHT_GREEN
                ;;
            "light yellow")
                COLOR=$LIGHT_YELLOW
                ;;
            "light blue")
                COLOR=$LIGHT_BLUE
                ;;
            "light magenta")
                COLOR=$LIGHT_MAGENTA
                ;;
            "light cyan")
                COLOR=$LIGHT_CYAN
                ;;
            "white")
                COLOR=$WHITE
                ;;
            *)
                COLOR=$WHITE
                ;;
        esac

        # add to ps1 var - color, then letter, then the end formatter
        ps1+=$COLOR"${letters[$i]}"

        # reset color index if we are at the end of the color array, otherwise increment it
        if (( $color_index == ${#colors_array[@]} - 1 ))
        then
            color_index=0
        else
            ((color_index=color_index+1))
        fi
    }
    ps1+="$END_FORMATTING\]"

    # Finally: set the PS1 variable
    PS1=$ps1
}

# Set the special bash variable PROMPT_COMMAND to our custom function
PROMPT_COMMAND=buildColorPrompt;

zsh

function buildColorPrompt() {

    # I always like showing what directory I am in
    directory=$(pwd)

    # Modify these to whatever you'd like!
    PROMPT_TEXT="youruser@yourmachine [$directory]"

    # Comma seperated colors - as many or as few as you'd like
    PROMPT_COLORS="15"

    # This will be the color of everything in the input part of the prompt (here set to 15 = white)
    PROMPT_INPUT_COLOR="15"

    # split PROMPT_COLORS into array
    colors_array=("${(@s/,/)PROMPT_COLORS}") # @ modifier

    # break PROMPT_TEXT into character array
    letters=()
    for (( i=1 ; i < ${#PROMPT_TEXT}+1 ; i++ )) {
        letters[$i]=${PROMPT_TEXT:$i-1:1}
    }

    # build prompt with colors
    color_index=1
    ps1=""
    for (( i=1 ; i < ${#letters[@]}+1 ; i++ )) {
        # Determine color in this giant case statement
        color="${colors_array[color_index]}"

        # add to ps1 var - color, then letter, then the end formatter
        ps1+="%F{$color}${letters[$i]}"

        # reset color index if we are at the end of the color array, otherwise increment it
        if (( $color_index == ${#colors_array[@]} ))
        then
            color_index=1
        else
            ((color_index=color_index+1))
        fi
    }

    # end color formating
    ps1+="%F{$PROMPT_INPUT_COLOR} %# "

    # Finally: set the PROMPT variable
    PROMPT=$ps1
}

# set the precmd() hook to our custom function
precmd() {
   buildColorPrompt;
}

You'll also find these snippets under the devops section of the snippets page.

Go ahead and paste these right into your .bash_profile or .zprofile, respectively, depending on what terminal you are using. Or, make a pull request on the repository to submit the code for a new shell!

Thanks!

That's all for now. I'm off to enjoy my birthday, but I may at some point write an additional post with a walkthrough of both functions - as it turns out, there are some annoying and interesting differences between bash and zsh - the most unforgivable is that loops in zsh start at 1 🙄.

Cheers! 🍺

Chris

Next / Previous Post:

Find more posts by tag:

-~{/* */}~-