Handy functions for colorizing terminal output.
Download technicolor
and put in on your path.
wget https://raw.githubusercontent.com/edemko/technicolor/master/technicolor -O /bin/technicolor
It's a Bourne Shell script, so it should require nothing special on Unixen and Unix-alikes.
Technicolor can be used as an executable:
technicolor test
Print number of colors supported. If there are few enough colors, each code will be printed in the corresponding color.technicolor code <RGB>
Print the terminal color code nearest the given rgb-space hex code. The code is colorized, and some background in that color is also produced. The leading hash is optional (but why would you ever actually use it?).technicolor <RGB> <TEXT> [FLAGS…]
acts as a colorized echo—like. Arguments are likewithaf
(see below), but trailing trailing newline isn't suppressed.technicolor <ARGS> <RGB> <TEXT> [FLAGS…]
acts as a colorized echo. Arguments are likewithcol
(see below), but trailing trailing newline isn't suppressed. Note that<ARGS>
here is mandatory, as otherwise it will act aswithaf
.technicolor
(with no arguments) does nothing.
Or it can be sourced
—source "$(which technicolor)"
or . "$(which technicolor)"
—
to provide a number of functions:
withcol <ARGS…> <TEXT>
WhereARGS
are any combination of-f<RGB>
,-b<RGB>
or-a<ATTR>
for foreground, background, and attribute respectively. Colors are interpreted withrgb2sgr
. Recognized attributes arebold
,ul
,rev
,blink
,invis
,so
, andul
. This sets the colors and attributes (later flags may override earlier ones), prints theTEXT
(i.e. everything afterARGS
is passed toecho -n
), and finally resets all colors and attributes (i.e.tput sgr0
). IfARGS
includes a-s
flag, then the reset code is not produced. Though it is possible to usewithcol ''
to clear attributes, I recommend usingnocol
. This is intended for use colorizing part of a larger string, as inecho "$(withcol -f00 -b [ERROR]) bus factor was too low!"
; to include a trailing newline, usetechnicolor
as a script instead.withaf <RGB> <TEXT> <ARGS...>
Aswithcol
, but only sets the foreground color. This is included because it is such a common use case. This is intended for use colorizing part of a larger string, as inecho "$(withaf -ff0 [WARNING]) look both ways before crossing"
; to include a trailing newline, usetechnicolor
as a script instead.nocol
Clear all colors and attributes.rgb2sgr
Convert a 3- or 6-digit RGB hexcode into the nearest code fortput
.
Terminals may not (tend not to?) support 24-bit color.
In fact, the ones I use commonly come with at best 256 colors like it was 1995.
Nevertheless, technicolor
accepts hexcodes, and attempts to select a nearby
terminal color.
These hexcodes may be specified as either full six-digit hexcodes or as three-digit hexcodes.
The three-digit codes are equivalent to the six-digit codes obtained by multiplying each channel by 16.
E.g. 70f
=== 7000f0
.
Note that there is no leading #
, as this would be quite inconvenient in shell.
Nevertheless, we accept and ignore a leading hash, since it may be generated by another program.
Since terminal color spaces can be quite baroque, I will now detail the algorithms I use to select an approximate color.
I use three different strategies to select the correct color from xterm256 color space. First, the 16 ANSI colors are given special treatment, then greyscale is attempted when appropriate, and finally an RGB color cube is used. See bash-ui for more information on this color encoding.
Known "primary" colors use the ansi16 subspace (codes 0-15).
In general, these match the regex [0d]{3}
for 0-7,
and [0f]{3}
for 8-15,
but a handful don't follow this pattern (see list by).
I retrieved these by approximating the xterm colors listed on
Wikipedia
so that they could be produced with three-digit codes.
When specifying a six-digit number, primaries are detected when
the low nybble in each channel is 0
,
but if the high nybble if f
, then the low nybble is also allowed to be f
without changing the color.
00f
is "blue" (replacing00d
)eee
is "white" (replacingddd
)888
is "bright black"55f
is "bright blue" (00f
is used for "blue")
If all three channels are identical, the grey26 subspace—a 26-color greyscale space—is used. I simply perform a linear transform from 0-255 to 0-25. (modulo integer division inaccuracies).
Finally, xterm256 includes an rgb216 subspace, which is sort-of a 6x6x6 RGB color cube.
Each channel is linearly transformed from 0-255 to 0-5 (again modulo integer division inaccuracies).
The actual "cube" quite unintuitive unfortunately; e.g. #77f
looks rather more lavender than light blue to me.
Nevertheless, programmers who are too creative for the plain ANSI codes can always test-and-check with technicolor test
.
Hexcodes have a well-defined semantics outside of any particular color encoding scheme. Thus, if you use a hexcode instead of directly using ANSI or SGR codes, your colors will be "more" "portable".
I'm open to adding support for specifying SGR codes directly, as long as they can be translated into other color spaces "reliably". Of course, HSV would be great as well, I just don't want to do the math when RGB gets me where I personally need to go.
I may even decide someday to allow for named colors configurable by the system.
That way, one could write green
to specify colors like "the user's favorite green", or some such nonsense.
At the moment, I am only handling 256-color terminals. Support for 16-color terminals could be added relatively easily. I don't currently have a good handle on the capabilities for true color (16 million colors).
True color seems to be as easy as echo -e "\e[38;2;${r};${g};${b}m"
(foreground) and echo -e "\e[48;2;${r};${g};${b}m"
background.
The real question is how to determine it per-terminal rather than setting it for the box (which may be connected to by an unexpected terminal)?