Skip to main content
Tweeted twitter.com/StackCodeReview/status/960245995215949826
edited tags
Link
200_success
  • 145.7k
  • 22
  • 191
  • 481
Source Link

Bash function to generate colored output

I like to user colored output in my interactive shell scripts.

I've been using Ubuntu and bash for a long time, but haven't done much bash scripting.

I'd appreciate a review of this function for generating colored output.

#!/usr/bin/env bash
#
# Function to generate colored output.
#
# The output is specified using a format string and arguments, similar to printf.
#
# All special format specifiers are enclosed in parenthesis.
# Colors start with either "f" for foreground or "b" for background with the
# remainder of the specifier being the color name, starting with a capital
# letter (e.g., fRed or bCyan).
#
# There are a few other specifiers in addition to the colors:
# - %s%         Placeholder; consumes the next argument from the command line
# - %u%         Underline
# - %r$         Reverse video
# - %default%   Default foreground and background color
# - %reset%     Reset all attributes
#
# Example:
#   echoc '%fRed%The value "%r%%s%%fRed%" is not valid for the --trace option' extra
# Result:
#   The value "extra" is not valid for the --trace option
#   with everything except the word extra being red on the default background
#   color and the word extra being reverse-video.
#
unset -f echoc
function echoc()
{
  local       char
  local       code
  local       fmt="${1}"
  local       fmtLen=${#fmt}
  local       i=0
  local       output=''
  local       str=''
  local       tagActive=0

  shift       # Remove the format string.

  #
  # The ANSI color and format codes.
  # Keys that start with "f" are foreground colors.
  # Keys that start with "b" are background colors.
  # Other keys are eight resets or other formatting codes.
  # The "r" and "u" are reverse and underscore, respectively.
  #
  declare -A colors
  colors=(['fBlack']='30' ['fBlue']='34' ['fBrown']='33' ['fCyan']='36')
  colors+=(['fDarkgray']='1;30' ['fDefault']='39' ['fGreen']='32' ['fLavender']='1;34')
  colors+=(['fLightblue']='1;36' ['fLightgray']='37' ['fLightgreen']='1;32')
  colors+=(['fLightpurple']='1;35' ['fMagenta']='35' ['fPink']='1;31' ['fPurple']='35')
  colors+=(['fRed']='31' ['fWhite']='1;37' ['fYellow']='1;33')
  colors+=(['bBlack']='40' ['bBlue']='44' ['bBrown']='43' ['bCyan']='46' ['bDefault']='49')
  colors+=(['bGreen']='42' ['bMagenta']='45' ['bPurple']='45' ['bRed']='41')
  colors+=(['default']='39;49' ['reset']='0')
  colors+=(['r']='7' ['u']='4')


  #
  # Parse the format string, building the output string as we go.
  #
  while [ $i -lt $fmtLen ]
  do
    char="${fmt:i:1}"
    case $char in
      %)
        if [ 0 -eq $tagActive ]
        then
          if [ -n "${str}" ]
          then
            output="${output}${str}"
            str=''
          fi
          tagActive=1
        else # tagActive is true.
          if [ -z "${str}" ]
          then
            output="${output}%" # %% inserts a percent sign.
          else
            if [ 's' == "${str}" ]
            then
              output="${output}${1}\033[0m"
              shift
            else
              code="${colors[$str]}"
              if [ -z "${code}" ]
              then
                echo -e "\033[31mInvalid color name \"${str}\"\033[0m"
              else
                output="${output}\033[${code}m"
              fi
            fi
            str=''
          fi
          tagActive=0
        fi
        ;;

      *)
        str="${str}${char}"
        ;;
    esac
    ((i++))
  done
  if [ -n "${str}" ]
  then
    output="${output}${str}"
  fi
  output="${output}\033[0m"
  echo -e "${output}"

}
export -f echoc