#!/bin/bash # ====================================================================== # # COLORS # # a few shell functions for colored output # # ---------------------------------------------------------------------- # License: GPL 3.0 # Source: <https://github.com/axelhahn/bash_colorfunctions> # Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/> # ---------------------------------------------------------------------- # 2023-08-09 ahahn 0.1 initial lines # 2023-08-09 ahahn 0.2 hide output of regex test with grep # 2023-08-13 ahahn 0.3 introduce of color presets with foreground and background # 2023-08-13 ahahn 0.4 list presets, debug, count of colors # 2023-08-13 ahahn 0.5 support of RGB hex code # 2023-08-14 ahahn 0.6 fix setting fg and bg as RGB hex code # 2023-08-14 ahahn 0.7 remove color.ansi; respect NO_COLOR=1 # 2023-08-16 ahahn 0.8 add function color.preset # ====================================================================== _VERSION=0.8 typeset -i COLOR_DEBUG; COLOR_DEBUG=0 # ---------------------------------------------------------------------- # CONSTANTS # ---------------------------------------------------------------------- declare -A BGCOLOR_CODE declare -A COLOR_CODE # background colors BGCOLOR_CODE[black]="40" BGCOLOR_CODE[red]="41" BGCOLOR_CODE[green]="42" BGCOLOR_CODE[brown]="43" BGCOLOR_CODE[blue]="44" BGCOLOR_CODE[purple]="45" BGCOLOR_CODE[cyan]="46" BGCOLOR_CODE[lightgray]="47" BGCOLOR_CODE[darkgray]="1;40" BGCOLOR_CODE[lightred]="1;41" BGCOLOR_CODE[lightgreen]="1;42" BGCOLOR_CODE[yellow]="1;43" BGCOLOR_CODE[lightblue]="1;44" BGCOLOR_CODE[lightpurple]="1;45" BGCOLOR_CODE[lightcyan]="1;46" BGCOLOR_CODE[white]="1;47" # foreground colors COLOR_CODE[black]="30" COLOR_CODE[red]="31" COLOR_CODE[green]="32" COLOR_CODE[brown]="33" COLOR_CODE[blue]="34" COLOR_CODE[purple]="35" COLOR_CODE[cyan]="36" COLOR_CODE[lightgray]="37" COLOR_CODE[darkgray]="1;30" COLOR_CODE[lightred]="1;31" COLOR_CODE[lightgreen]="1;32" COLOR_CODE[yellow]="1;33" COLOR_CODE[lightblue]="1;34" COLOR_CODE[lightpurple]="1;35" COLOR_CODE[lightcyan]="1;36" COLOR_CODE[white]="1;37" # custom presets as array of foreground and background color # # +--- the label is part of the variable # | # v # COLOR_PRESET_error=("white" "red") # COLOR_PRESET_ok=("white" "green") # ---------------------------------------------------------------------- # PRIVATE FUNCTIONS # ---------------------------------------------------------------------- # write debug output - if debugging is enabled # Its output is written to STDERR # param string text to show function color.__wd(){ test "$COLOR_DEBUG" = "1" && >&2 echo "DEBUG: $*" } # test, if given value is a known color name # param string colorname to test function color.__iscolorname(){ test -n "${COLOR_CODE[$1]}" && return 0 return 1 } # test, if given value is a value 0..7 # param string color to test function color.__iscolorcode(){ test "$1" = "0" && return 0 test "$1" = "1" && return 0 test "$1" = "2" && return 0 test "$1" = "3" && return 0 test "$1" = "4" && return 0 test "$1" = "5" && return 0 test "$1" = "6" && return 0 test "$1" = "7" && return 0 return 1 } # test, if given value is an ansi code # param string color to test function color.__iscolorvalue(){ if grep -E "^([01];|)[34][0-7]$" >/dev/null <<< "$1" ; then return 0 fi return 1 } # test, if given value is an rgb hexcode eg. #80a0f0 # param string color to test function color.__isrgbhex(){ if grep -iE "^#[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$" >/dev/null <<< "$1" ; then return 0 fi return 1 } # convert rgb hex code eg. #80a0f0 to 3 decimal values # output us a string with space separated values for red, green, blue # param string color as "#RRGGBB" to convert function color.__getrgb(){ local _r local _g local _b if color.__isrgbhex "$1"; then _r=$( cut -c 2,3 <<< "$1" ) _g=$( cut -c 4,5 <<< "$1" ) _b=$( cut -c 6,7 <<< "$1" ) echo "$((16#$_r)) $((16#$_g)) $((16#$_b))" fi } # test, if given value is a color that can be one of # - colorname # - value 0..7 # - ansi code # param string color to test function color.__isacolor(){ if color.__iscolorname "$1"; then return 0; fi if color.__iscolorcode "$1"; then return 0; fi if color.__iscolorvalue "$1"; then return 0; fi if color.__isrgbhex "$1"; then return 0; fi color.__wd "$FUNCNAME is acolor: $1 --> No" return 1 } # test, if given value is an existing preset # param string color to test function color.__isapreset(){ local _colorset eval "_colorset=\$COLOR_PRESET_${1}" test -n "$_colorset" && return 0 return 1 } # respect NO_COLOR=1 # return 1 if colors are allowed to be used. function color.__usecolor(){ test "$NO_COLOR" = "1" && return 1 return 0 } # set foreground or background # param string color as # - basic color 0..7 OR # - color name eg. "black" OR # - a valid color value eg. "1;30" OR # - a hex code eg. "#10404f" # param integer what to set; '3' for for foreground or '4' for background colors function color.__fgorbg(){ local _color="$1" local _prefix="$2" color.__wd "$FUNCNAME $1 $2" if color.__iscolorname "${_color}"; then color.__wd "yep, ${_color} is a color name." test "$_prefix" = "3" && color.set "${COLOR_CODE[${_color}]}" test "$_prefix" = "4" && color.set "${BGCOLOR_CODE[${_color}]}" else if color.__iscolorcode "${_color}"; then color.__wd "yep, ${_color} is a color code." else if color.__iscolorvalue "${_color}"; then color.__wd "yep, ${_color} is a color value." color.set "${_color}" else if color.__isrgbhex "${_color}"; then local _r local _g local _b read -r _r _g _b <<< $( color.__getrgb "${_color}" ) color.set "${_prefix}8;2;$_r;$_g;$_b" else >&2 echo "ERROR: color '${_color}' is not a name nor a value between 0..7 nor a valid color value nor RGB." fi fi fi fi } # ---------------------------------------------------------------------- # FUNCTIONS :: helpers # ---------------------------------------------------------------------- # get count of colors in the current terminal function color.count(){ tput colors } # enable debug flag function color.debugon(){ COLOR_DEBUG=1 color.__wd "$FUNCNAME - debugging is enabled now" } # disable debug flag function color.debugoff(){ color.__wd "$FUNCNAME - disabling debugging now" COLOR_DEBUG=0 } # show debugging status function color.debugstatus(){ echo -n "INFO: color.debug - debugging is " if [ $COLOR_DEBUG -eq 0 ]; then echo "DISABLED" else echo "ENABLED" fi } # show help function color.help(){ local _self; _self='[path]/color.class.sh' color.reset local _debug=$COLOR_DEBUG COLOR_DEBUG=0 echo "_______________________________________________________________________________" echo color.echo "red" " ### ### # ### ####" color.echo "yellow" " # # # # # # # #" color.echo "white" " # # # # # # ####" color.echo "yellow" " # # # # # # # #" color.echo "red" " ### ### ##### ### # #" echo "_________________________________________________________________________/ v$_VERSION" echo sed "s#^ ##g" << EOH HELP: 'color' is a class like component to simplify the handling of ansi colors and keeps the color settings readable. A set NO_COLOR=1 will be respected. Author: Axel Hahn License: GNU GPL 3.0 Source: <https://github.com/axelhahn/bash_colorfunctions> Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/> FUNCTIONS: ---------- Information: color.help this help color.list show a table with valid color names color.presets show a table with defined custom presets color.count get count of colors in the current terminal color.debugon enable debugging color.debugoff disable debugging color.debugstatus show debugstatus ---------- Colored output: color.bg COLOR (COLOR2) set a background color; a 2nd parameter is optional to set a foreground color too color.fg COLOR (COLOR2) set a foreground color; a 2nd parameter is optional to set a background color too color.preset PRESET Apply the color set of foreground and background of a given preset name. color.echo COLOR|PRESET (COLOR2) TEXT write a colored text with carriage return and reset colors The 1st param must be a COLOR(code/ name) for the foreground or a label of a preset. The 2nd CAN be a color for the background, but can be skipped. Everything behind is text for the output. color.print COLOR|PRESET (COLOR2) TEXT see color.echo - the same but without carriage return. color.reset reset colors color.set RAWCOLOR (RAWCOLOR2 (... RAWCOLOR_N)) set ansi colors; it can handle multiple color values ---------- Other: color.blink start blinking text color.bold start bold text color.invert start inverted text color.underline start underline text VALUES: COLOR a color; it can be... - a color keyword, eg black, blue, red, ... for all known values run 'color.list' - a value 0..7 to set basic colors 30..37 (or 40..47) - an ansi color value eg. "30" or "1;42" - RGB hexcode with '#' as prefix followed by 2 digit hexcode for red, green and blue eg. "#10404f" (like css rgb color codes) PRESET Name of a custom preset; see DEFINE PRESETS below. RAWCOLOR an ansi color value eg. "30" (black foreground) or "1;42" (lightgreen background) DEFINE PRESETS: A shortcut for a combination of foreground + background color. The label is part of a bash variable with the prefix 'COLOR_PRESET_'. The value is a bash array with 2 colors for foreground and background. See the value description for COLOR above. SYNTAX: COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>) To see all defined presets use 'color.presets' EXAMPLES: First you need to source the file $_self. . $_self (1) Show output of the command 'ls -l' in blue color.fg "blue" ls -l color.reset (2) show a red error message color.echo "red" "ERROR: Something went wrong." (3) Use a custom preset: COLOR_PRESET_error=("white" "red") color.echo "error" "ERROR: Something went wrong." This defines a preset named "error". "white" is a colorname for the foreground color, "red" ist the background. EOH if [ -n "$NO_COLOR" ]; then echo -n "INFO: NO_COLOR=$NO_COLOR was set. The coloring functionality is " if ! color.__usecolor; then echo "DISBALED." else echo "ENABLED (must be 1 to disable)." fi echo else echo "INFO: NO_COLOR will be respected - but it is not set." fi COLOR_DEBUG=$_debug } # a little helper: show colors and the color codes function color.list(){ color.reset local _debug=$COLOR_DEBUG COLOR_DEBUG=0 echo echo "List of colors:" echo echo "--------------------------------------------------" echo "color | foreground | background" echo "--------------------------------------------------" for i in "${!COLOR_CODE[@]}" do printf "%-15s %4s " $i ${COLOR_CODE[$i]} color.set "${COLOR_CODE[$i]}" color.set "40" printf " Test " color.set "1;47" color.set "${COLOR_CODE[$i]}" printf " Test " color.reset printf " %5s " ${BGCOLOR_CODE[$i]} color.set ${BGCOLOR_CODE[$i]} printf " Test " color.reset echo done | sort color.reset echo "--------------------------------------------------" echo COLOR_DEBUG=$_debug } # little helper: sow defined presets and its preview function color.presets(){ local _label local _value local _colorvar local _fg local _bg color.reset local _debug=$COLOR_DEBUG COLOR_DEBUG=0 if ! set | grep "^COLOR_PRESET_.*=(" >/dev/null; then echo "INFO: No preset was defined yet." echo "To set one define shell variables with an array of 2 colors:" echo " COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>)" echo "For more help call 'color.help' or see the docs." else echo echo "List of presets:" echo echo "---------------------------------------------------------------------" echo "label | foreground | background | example" echo "---------------------------------------------------------------------" set | grep "^COLOR_PRESET_.*=(" | while read -r line do _label=$( cut -f 1 -d '=' <<< "$line" | cut -f 3- -d '_') _example=$( color.print "$_label" "example for peset '$_label'" ) _colorvar="COLOR_PRESET_${_label}" eval "_fg=\${$_colorvar[0]}" eval "_bg=\${$_colorvar[1]}" printf "%-10s | %-12s | %-12s | %-50s\n" "$_label" "${_fg}" "${_bg}" "$_example" done echo "---------------------------------------------------------------------" echo fi COLOR_DEBUG=$_debug } # ---------------------------------------------------------------------- # FUNCTIONS :: set color # ---------------------------------------------------------------------- # set background color # param string backround color 0..7 OR color name eg "black" or a valid color value eg "1;30" # param string optional: foreground color function color.bg(){ color.__wd "$FUNCNAME $1" color.__fgorbg "$1" 4 test -n "$2" && color.fg "$2" } # get a color of a preset # param string name of preset # param integer array index; 0= foreground; 1= background function color.__getpresetcolor(){ local _label=$1 local _index=$2 local _colorvar _colorvar="COLOR_PRESET_${_label}" eval "echo \${$_colorvar[$_index]}" } # set foreground color # param string foreground color 0..7 OR color name eg "black" or a valid color value eg "1;30" # param string optional: background color function color.fg(){ color.__wd "$FUNCNAME $1" color.__fgorbg "$1" 3 test -n "$2" && color.bg "$2" } # set colors of a preset # param string label of a preet function color.preset(){ if color.__isapreset "$1"; then local _colorvar local _colfg=$( color.__getpresetcolor "$1" 0) local _colbg=$( color.__getpresetcolor "$1" 1) color.reset test -n "$_colfg" && color.__fgorbg "$_colfg" 3 test -n "$_colbg" && color.__fgorbg "$_colbg" 4 else >&2 echo "ERROR: this value is not a valid preset: $1. See 'color.presets' to see current presets." fi } # ---------------------------------------------------------------------- # reset all colors to terminal default function color.reset(){ color.__wd "$FUNCNAME" color.set "0" } # start bold text function color.bold(){ color.__wd "$FUNCNAME" color.set "1" } # start underline text function color.underline(){ color.__wd "$FUNCNAME" color.set "4" } # start blinking text function color.blink(){ color.__wd "$FUNCNAME" color.set "5" } # start inverted text function color.invert(){ color.__wd "$FUNCNAME" color.set "7" } # ---------------------------------------------------------------------- # write ansicode to set color combination # param string color 1 as ansi value # param string color N as ansi value function color.set(){ local _out= if color.__usecolor; then for mycolor in $* do color.__wd "$FUNCNAME: processing color value '${mycolor}'" _out+="${mycolor}" done color.__wd "$FUNCNAME: output is '\e[${_out}m'" printf "\e[${_out}m" else color.__wd "$FUNCNAME: skipping - coloring is disabled." fi } # ---------------------------------------------------------------------- # FUNCTIONS :: print # ---------------------------------------------------------------------- # show a colored text WITH carriage return # param string foreground color as code / name / value # param string optional: background color as code / name / value # param string text to print function color.echo(){ color.__wd "$FUNCNAME $*" local _param1="$1" local _param2="$2" shift 1 shift 1 color.print "$_param1" "$_param2" "$*" echo } # show a colored text without carriage return # param string foreground color as code / name / value or preset # param string optional: background color as code / name / value # param string text to print function color.print(){ color.__wd "$FUNCNAME $*" if color.__isacolor "$1"; then if color.__isacolor "$2"; then color.fg "$1" "$2" shift 1 shift 1 else color.fg "$1" shift 1 fi elif color.__isapreset "$1"; then color.preset "$1" shift 1 else >&2 echo -n "ERROR: Wrong color values detected. Command was: colors.print $*" fi echo -n "$*" color.reset } # ======================================================================