Ecos — Echo with millions of colors (Bash package)

Below you find the latest version of (1) the package's README and (2) its main source file.

For the git repository and issue tracker, see the project's page on sr.ht.

For more packages, see Software.


README.org

Colors and terminals

Let's talk about adding some colors to that black screen you spend all day staring at.

Adding a bit of color

If you want to add color to text that shows up in your terminal, you'd normally do something like this:

echo -e '\e[1;31mHello\e[1;32m, \e[1;33mworld\e[1;35m!\e[0m'

Soon you get tired and define the colors. Then it becomes something like this:

echo -e "${BOLD_RED}Hello${BOLD_GREEN}, ${BOLD_YELLOW}world${BOLD_MAGENTA}!${NOCOLOR}"

which is longer, but definitely more readable.

With ecos, you'd do just this:

ecos 1/red Hello /green ", " /yellow world /magenta !

Isn't that much better than the previous two?

Yet one day you wake up wondering:

  • Why have you been limiting yourself to eight (or sixteen-ish) colors as if we were still in the 80s?
  • Your computer can show you high-resolution movies, with millions of colors. So why is your terminal skimping on them?
    Why has it been offering you just a meagre mix of primary colors — of the sort you'd only see in printer tests and in tacky coupons of fast-food joints?
  • Couldn't you have a few more colors?
    Perhaps a purple, and an orange, and a few non–eye-watering varieties of cyan and magenta?

Adding more bits

Long story short, yes, you could.

You can have 24 bits of colors in your terminal. That's 224 = 16 777 216 colors to choose from.

There's a snag, though: typing them can be even more awkward:

echo -e '\e[1;38;2;255;165;0mOrange!\e[0;38;2;0;139;139m and \e[1;38;2;160;32;240mPurple!\e[0m'

But ecos can also deal with those:

ecos 1.orange Orange! 0.cyan4 ' and ' 1.purple Purple!

All X11 color names are available — more than 500 colors that you can call by name.

Entering colors

You now do the math and realize that the number 500 is a bit smaller than 16 777 216. Alas, we can't name all these colors.

But that's ok: you can enter any of them as RGB combinations, either as decimal triplets:

ecos .120.201.122 nameless-green

or as hex:

ecos .#78c97a nameless-green

And adding a background color is as simple as:

ecos  .#78c97a_.#873685      nameless-green-on-nameless-purple
ecos 1.#78c97a_.#873685 bold-nameless-green-on-nameless-purple

Time to show you a ton of examples.

Examples

These come straight from examples-ecos, which you can run locally. Try:

# make it executable
chmod +x ./examples-ecos

# help
./examples-ecos -h

# see them all
./examples-ecos

# pick some option
./examples-ecos ov   # ov|op|ef|4|8|24

# select from a menu
./examples-ecos -i

Let's see them all.

Note

Besides colors, you can apply effects to text, such as bold, italic, and underline. The examples show these as well.

Even though some of the effects mentioned below (notably blink and double underline) might not appear in your browser, they'd likely show alright in your terminal.

Overview


[Overview]

# Usage:
#   examples-ecos
#   examples-ecos [-i|ov|op|ef|4|8|24]
#
# Overview: common syntax for colors.
#
# Syntax: ecos <effect><depth><color>_<effect><depth><color> <string1> …
#
# - That's foreground_background. Either can be omitted.
# - Effects are passed through zero or more comma-separated numbers.
# - There're many effects; the most common are 0 (reset) and 1 (bold).
# - <depth> is one of [/:.] for 4, 8, 24-bits respectively.
# - You can use different depths for foreground and background, if you want.
# - <color>'s format depends on the depth.
#
# These should give you some idea:
ecos 1/2_/blue "Bold G on B" 0 ", " _1:208 "bold orange bg" 0 ", " .orange orange
Bold G on B, bold orange bg, orange

ecos .lightyellow1_.#f00 "lightyellow1 on red" 0 ", " .0.238.118 springgreen2
lightyellow1 on red, springgreen2

ecos 1,3.#ff4500_.#fff5ee "bold italic orangered1 on seashell"
bold italic orangered1 on seashell

ecos 1,3.orangered1_.seashell "bold italic orangered1 on seashell"
bold italic orangered1 on seashell

# And there's a good self-color-testing option with -s:
ecos -s 1/red 0/blue 1:208 3.#f00 .#c9fe02 1.#ff40a7 .200.27.140 1.orange1_.navy
1/red 0/blue 1:208 3.#f00 .#c9fe02 1.#ff40a7 .200.27.140 1.orange1_.navy

# See "Options" (op) for command line options.
# See "Effects" (ef) for common effects on color.
# See each depth (4, 8, 24) for more examples.

Options


[Options]

# These are the available command line options.
#
# - REGULAR mode
#   With or without newline:
ecos    1/blue "Regular mode."
Regular mode.

ecos -n 1/blue "Suppress trailing newline; you can add one with 0 '\​n'"
Suppress trailing newline; you can add one with 0 '\​n'

#   With or without between-pairs reset of effects:
#   (Note that effects accumulate in the latter. This is the default.)
ecos -r 1.red Hello 3.green There 4.skyblue3 World
HelloThereWorld

ecos    1.red Hello 3.green There 4.skyblue3 World
HelloThereWorld

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - SELF mode: use -s to self-colorize. (Between-pairs reset is implied.)
ecos -s i/blue :208 1.orange_.navy 53.skyblue1
i/blue :208 1.orange_.navy 53.skyblue1

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - EFFECT mode: use -e to showcase common effects applied to these colors.
ecos -e .skyblue1 /green :200

0.skyblue1   0/green   0:200
1.skyblue1   1/green   1:200
2.skyblue1   2/green   2:200
3.skyblue1   3/green   3:200
4.skyblue1   4/green   4:200
5.skyblue1   5/green   5:200
7.skyblue1   7/green   7:200
9.skyblue1   9/green   9:200
53.skyblue1  53/green  53:200
21.skyblue1  21/green  21:200

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - OPEN mode:  use -o to turn color on  — commands after it will inherit it.
#   To stop it: use -O to turn color off — equivalent to: ecos -n 0 "".
ecos -o 1,3/blue;  echo -n Hello; echo " there!"
Hello there!

ecos -o 1,3.orange; printf Hello; printf " there, "; ecos -O; echo dear World.
Hello there, dear World.

ecos -o 1,3.orange         Hello; printf " there, "; ecos -O; echo dear World.
Hello there, dear World.

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - FILTER mode: use -f to apply colors to piped text.
#   Use sed-like address and counter as 3rd and 4th args.
#
#   Color all 'a' green, 2nd 'p' red, 3rd 'p' cyan, all 'e' blue
ecos -f .green a . . .red p . 2 .cyan p . 3 .blue e . . <<<"apple banana grape"
apple banana grape

#   Color red the 1st character of every line ending in 7 (then fold to 1 line)
seq 5 18 | ecos -f 1/red ^. /7$/ . | xargs
5 6 7 8 9 10 11 12 13 14 15 16 17 18

#   Color 1s red, 2s green, 5s yellow, and the 4th line blue
seq 5 20 | ecos -f 1/red 1 . . i/green 2 . . 1/yellow 5 . . 1/blue . 4 . | xargs
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

#   Color 1s red, 2s green, 5s yellow, and blue the last char of lines 3, 8, 13...
seq 6 20 | ecos -f 1/red 1 . . i/green 2 . . 1/yellow 6 . . 1/blue .$ 3~5 . | xargs
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - UTILITY mode: use -u for an assortment of color utilities.
#   Use these to convert between decimal triplets, hex triplets, and names
#   (decimal triplets can be dot-delimited; some color names are synonymous)
ecos -u d2h 255.99.71 238.92.66

ff6347
ee5c42

ecos -u h2d ff6347 ee5c42 f00

255;99;71
238;92;66
255;0;0

ecos -u d2n 255.99.71 238.92.66

tomato1 tomato
tomato2

ecos -u h2n ff6347 ee5c42 f00

tomato1 tomato
tomato2
red1 red

ecos -u n2d tomato1 tomato2

255;99;71
238;92;66

ecos -u n2h tomato1 tomato2

ff6347
ee5c42

#   Use these to see the list of color names (not shown here — try them!)
#   - lnh and lnd show hex and decimal codes of colors (opt. $1: delimiter)
# ecos -u lnh       ;   ecos -u lnd
# ecos -u lnh s     ;   ecos -u lnd s
# ecos -u lnh ,     ;   ecos -u lnd t
#
#   - Use lnc to show colors in their own color (opt. rx=$1, columns=$2)
# ecos -u lnc       ;   ecos -u lnc blue
# ecos -u lnc . 1   ;   ecos -u lnc blue 1
# ecos -u lnc . 4   ;   ecos -u lnc blue 4
#
#   Ok, ok. Let's show a few:
ecos -u lnc pur 5

mediumpurple1  mediumpurple2  mediumpurple3  mediumpurple4  mediumpurple
purple1        purple2        purple3        purple4        purple

ecos -u lnc en3$

          darkolivegreen3
          darkseagreen3
          green3
          palegreen3
          seagreen3
          springgreen3

ecos -u find 42

          brown         a52a2a  165  42   42
          cadetblue2    8ee5ee  142  229  238
          goldenrod2    eeb422  238  180  34
          gray14        242424  36   36   36
          gray26        424242  66   66   66
          gray42        6b6b6b  107  107  107
          gray95        f2f2f2  242  242  242
          lightsalmon4  8b5742  139  87   66
          olivedrab     6b8e23  107  142  35
          sienna2       ee7942  238  121  66
          tomato2       ee5c42  238  92   66

#   For more: ecos -u help

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - HELP mode: use -h to get help (not shown here).
# ecos -h
#┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
# [Developer Options]
#
# - FUNCTION HELP mode: use -hFUN to see internal ecos--FUN's docstring.
ecos -hhex2dec

ecos--hex2dec (hex1 _hex2 _hex3 …)
Convert lowercase HEX triplets to semicolon-separated decimal triplets.

#┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈ ┈
# - EXEC mode: use -=FUN to run function ecos--FUN
ecos -=hex2dec f00 c0ffee

255;0;0
192;255;238

# You most likely don't need these.
# (e.g., you'd normally get the latter with: ecos -u h2d f00 c0ffee).

Effects


[Effects]

# A few effects are usually available
ecos /4 regular. 1/4 bold. 0,2/4 dim. 0,3/4 italic.
regular.bold.dim.italic.

ecos 0,4/4 underline. 0,5/4 blink. 0,7/4 reverse. 0,8/4 hide.
underline.blink.reverse.hide.

ecos 0,9/4 strike.  0,21/4 "double underline."  0,53/4 overline.
strike.double underline.overline.

# Effects stay. That prepended "0," turns all off before passing new effect.
ecos 1/blue bold. 3 italic.   4     underline. 5 blink. 7 reverse.
bold.italic.underline.blink.reverse.

ecos 1/blue bold. 3 italic.   4/red underline. 5 blink. 7 reverse.
bold.italic.underline.blink.reverse.

ecos 1/blue bold. 3 italic. 0,4/red underline. 5 blink. 7 reverse.
bold.italic.underline.blink.reverse.

# You can turn only the color off with a "color 9". Compare with the above.
ecos 1/blue bold. 3 italic.   4/9   underline. 5 blink. 7 reverse.
bold.italic.underline.blink.reverse.

ecos 1/blue "bold blue" /9 " just bold"
bold blue just bold

# Comma-separate to turn on more than one effect at once
ecos 1,3/blue "bold and italic"
bold and italic

ecos 1,3,9/yellow_/red "Overloaded FOO."
Overloaded FOO.

# Though exemplified with 4-bit, effects also work on 8- and 24-bit colors.
# You can showcase common effects with the -e option:
ecos -e .coral1 /blue :220 .#c0ffee

0.coral1   0/blue   0:220   0.#c0ffee
1.coral1   1/blue   1:220   1.#c0ffee
2.coral1   2/blue   2:220   2.#c0ffee
3.coral1   3/blue   3:220   3.#c0ffee
4.coral1   4/blue   4:220   4.#c0ffee
5.coral1   5/blue   5:220   5.#c0ffee
7.coral1   7/blue   7:220   7.#c0ffee
9.coral1   9/blue   9:220   9.#c0ffee
53.coral1  53/blue  53:220  53.#c0ffee
21.coral1  21/blue  21:220  21.#c0ffee

# Note: dim (2) has no effect on 24-bit; and for 4-bit, bold implies intense.

4-bit


[4-bit]

# Use: <effect>/<color> — the "/" means "4-bit"
ecos 1/yellow "Hello, world! I'm bold yellow."
Hello, world! I'm bold yellow.

# A new color changes the previous
ecos 1/red "Hello" /green ", " /magenta world /cyan '!'
Hello, world!

# The 0 turns off every effect, so the " or " is in the default color and no bold
ecos 1/blue "By name" 0/cyan " or " 1/4 "by number" 0/cyan " (0–7, blue=4)"
By name or by number (0–7, blue=4)

# Bold and intense effects (intense is only for 4-bits; and no bold-regular here)
ecos 0/black   regular 0 ", " i/black   intense 0 ", " 1/black   bold-intense
regular, intense, bold-intense

ecos 0/red     regular 0 ", " i/red     intense 0 ", " 1/red     bold-intense
regular, intense, bold-intense

ecos 0/green   regular 0 ", " i/green   intense 0 ", " 1/green   bold-intense
regular, intense, bold-intense

ecos 0/yellow  regular 0 ", " i/yellow  intense 0 ", " 1/yellow  bold-intense
regular, intense, bold-intense

ecos 0/blue    regular 0 ", " i/blue    intense 0 ", " 1/blue    bold-intense
regular, intense, bold-intense

ecos 0/magenta regular 0 ", " i/magenta intense 0 ", " 1/magenta bold-intense
regular, intense, bold-intense

ecos 0/cyan    regular 0 ", " i/cyan    intense 0 ", " 1/cyan    bold-intense
regular, intense, bold-intense

ecos 0/white   regular 0 ", " i/white   intense 0 ", " 1/white   bold-intense
regular, intense, bold-intense

# Blue again, but by number
ecos 0/4       regular 0 ", " i/4       intense 0 ", " 1/4       bold-intense
regular, intense, bold-intense


8-bit


[8-bit]

# Use: <effect>:<color> — the ":" means "8-bit"
# Colors are available through a single number from 0 to 255:
ecos :208 orange 0 ", " 1:208 bold-orange
orange, bold-orange

# Colors 0–7 are the same as 4-bit regular; 8–15, 4-bit intense.
# Differently from 4-bit, you can have bold without intense.
# But there's no "i" — you do it by changing the color number. Here're all 8:
ecos 0:0 regular 0 ", " 0:8  intense 0 ", " 1:0 bold  0 ", " 1:8  bold-intense
regular, intense, bold, bold-intense

ecos 0:1 regular 0 ", " 0:9  intense 0 ", " 1:1 bold  0 ", " 1:9  bold-intense
regular, intense, bold, bold-intense

ecos 0:2 regular 0 ", " 0:10 intense 0 ", " 1:2 bold  0 ", " 1:10 bold-intense
regular, intense, bold, bold-intense

ecos 0:3 regular 0 ", " 0:11 intense 0 ", " 1:3 bold  0 ", " 1:11 bold-intense
regular, intense, bold, bold-intense

ecos 0:4 regular 0 ", " 0:12 intense 0 ", " 1:4 bold  0 ", " 1:12 bold-intense
regular, intense, bold, bold-intense

ecos 0:5 regular 0 ", " 0:13 intense 0 ", " 1:5 bold  0 ", " 1:13 bold-intense
regular, intense, bold, bold-intense

ecos 0:6 regular 0 ", " 0:14 intense 0 ", " 1:6 bold  0 ", " 1:14 bold-intense
regular, intense, bold, bold-intense

ecos 0:7 regular 0 ", " 0:15 intense 0 ", " 1:7 bold  0 ", " 1:15 bold-intense
regular, intense, bold, bold-intense

# Beyond 15, you have to look them up
ecos 1:20 20, :42 42, :50 50, :90 90, :142 142, :200 200, :210 210, :215 215
20,42,50,90,142,200,210,215

# From 232 to 255, it's all grayscale:
ecos 1:235 235_ :240 240_ :245 245_ :250 250_ :255 255_
235_240_245_250_255_

ecos -s :2{32..55}
:232 :233 :234 :235 :236 :237 :238 :239 :240 :241 :242 :243 :244 :245 :246 :247 :248 :249 :250 :251 :252 :253 :254 :255


24-bit


[24-bit]

# Use: <effect>.<color> — the "." means "24-bit"
#
# There are different ways to express the color part. You can use:
#
# 1. Simplified hex triplets (#0ac <=> #00aacc; #3e9 <=> #33ee99)
ecos 1.#f00 f00_ .#ace ace_ .#bad bad_ .#133 133_ .#1ee 1ee
f00_ace_bad_133_1ee

# 2. Full hex triplets
ecos 1.#729fbc 729fbc_ .#bee791 bee791_ .#beefed beefed
729fbc_bee791_beefed

ecos 1.#cafe0a cafe0a_ .#c0ffee c0ffee_ .#decaf9 decaf9
cafe0a_c0ffee_decaf9

ecos  .#decade decade_ .#facade facade_ .#faded1 faded1
decade_facade_faded1

# 3. Decimal triplets
ecos 1.114.159.188 729fbc_ .190.231.145 bee791_ .190.239.237 beefed
729fbc_bee791_beefed

ecos 1.202.254.10  cafe0a_ .192.255.238 c0ffee_ .222.202.249 decaf9
cafe0a_c0ffee_decaf9

ecos  .222.202.222 decade_ .250.202.222 facade_ .250.222.209 faded1
decade_facade_faded1

# 4. X11 color names
# (-s = self-color: use the color on itself; good for examples like these)
ecos -s .{cyan,seagreen}{1,2,3,4}
.cyan1 .cyan2 .cyan3 .cyan4 .seagreen1 .seagreen2 .seagreen3 .seagreen4

ecos 1.orange Camel .cyan2 Case .seagreen2 " snake" .seagreen4 _ .cyan2 case
CamelCase snake_case

# You can mix formats — no problem.
#
# Here're some examples using both foreground and background.
# Note that the bold effect ("1") can be in either part when you have both.
ecos -s 1.tomato _1.tomato 1_.tomato .#ff0_1.tomato 1.#ff0_.tomato
1.tomato _1.tomato 1_.tomato .#ff0_1.tomato 1.#ff0_.tomato

# In the above, the "1" is repeated because -s resets everything between
# one color and the other, so each color expression has to be complete.
# But in normal mode, previous effects/colors remain until you change them:
ecos 1.#ff0 FOO _.tomato foo .lightcyan2 FOO _.navy foo .tomato FOO _.9 foo _.#aaa FOO .9 foo _.9 FOO
FOOfooFOOfooFOOfooFOOfooFOO

# Unless you pass -r, in which case effects are reset at each step.
ecos -r 1.#ff0 FOO _.tomato foo .lightcyan2 FOO _.navy foo .tomato FOO _.9 foo _.#aaa FOO .9 foo _.9 FOO
FOOfooFOOfooFOOfooFOOfooFOO

# That "9", used in the color part, resets only the color.
# It means "default color".
# So .9 and _.9 reset foreground and background color, respectively.
#
# (There's also an effect "9": strike through, which "29" can remove.)
ecos 1,9.#ee0_.navy "Bold struck thru!" 29 " Remove strike. " 3 "Add italic. " .9 "Reset fgcolor. " _.9 "Reset bgcolor. " .tomato Tomato. 0 " Reset all."
Bold struck thru! Remove strike. Add italic. Reset fgcolor. Reset bgcolor. Tomato. Reset all.

# And here's the title and subtitle of a splendid website:
ecos .gray88_1.gray11 "              Listful Andrew               "; ecos .#ff4b4b_1.gray11 \` .gray55 \( .#e9b2e3 "emacs lisp bash " .#ff4b4b ,@ .#e9b2e3 functional-programming .gray55 \)

              Listful Andrew               
`(emacs lisp bash ,@functional-programming)

# Enjoy!

Completion

You can add completion to ecos, so that if you <tab><tab> with your cursor here:

ecos .li★

you'll see all 47 named colors that start with light, plus limegreen and linen.

This is the most useful completion, but it also works when you <tab><tab> while in other parts of an ecos command.

See ecos -h for instructions on how to enable it.

Known limitations

A couple completions don't work — but that's ok.

Completion won't work for background colors

Workaround:

  • type a space after the _ and complete the color as if it were foreground
  • then delete that added space.

Completion won't work for 8-bit colors

That's ok:

  • the syntax is trivial: any number between 0 and 255 (e.g., ecos :111 some-blue)
  • you'd still have to type these three digits anyway

Notes about these

I could have fixed both by adding this to the completion function:

COMP_WORDBREAKS="${COMP_WORDBREAKS//:}_"

Yet it's not a good idea: it'd change a global variable that could break other completions.

It's possible to work around this, but it was not worth the trouble in these cases.

Still, if you really want, you can change the variable yourself as above.
Then use ecos. Complete those cases.
Then either close that shell instance, or restore that variable with:

COMP_WORDBREAKS="${COMP_WORDBREAKS//_}:"

Installation

See my page Software for the most up-to-date instructions on how to download and install my packages.

Also run ecos -h and look into Completion and Sourcing.

Contributing

See my page Software for information about how to contribute to my packages.

News

0.2.0

Release

License

This project follows the REUSE Specification (FAQ), which in turn is built upon SPDX.

Therefore, license and copyright information can be found in:

  • each file's comment header, or
  • an adjacent file of the same name with the additional extension .license, or
  • the .reuse/dep5 file

The full text of the licenses can be found in the LICENSES subdirectory.


ecos

Structure

## Ecos --- Echo with millions of colors
## Commentary
### See the README for more information
## Code
### Functions
#### Main
#### Internal
##### Filter
##### Parse
###### Pairs
###### Colors
##### Color utilities
###### Select utility
###### Convert between color representation
####### Between hex and decimal triplets
####### To names from hex and decimal triplets
####### From names to hex and decimal triplets
###### Display color information
####### Display formatted data
####### Format raw data
####### Output raw data
##### Enable completion
##### Get help
### Source it or run it
## Ecos ends here

Contents

#!/usr/bin/env bash

## Ecos --- Echo with millions of colors

# SPDX-FileCopyrightText: © flandrew <https://flandrew.srht.site/listful>
# SPDX-License-Identifier: GPL-3.0-or-later

#---------------------#
# Author:  flandrew   #
# Created: 2025-12-22 #
# Updated: 2026-01-07 #
#---------------------#
# Version: 0.2.0      #
#---------------------#

## Commentary
#
# Ecos makes it easy to add colors and effects to text in your terminal.
#
# To know how to use it, run: ecos -h (or -H)
#
### See the README for more information
#
# A local README.org should be available in the same directory as this file.
#
# You can also read it online:
#   https://flandrew.srht.site/listful/sw-bash-ecos.html
#
#############################################################################

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
## Code
### Functions
#### Main

ecos()
{ : _options <<_
Ecos --- Echo with millions of colors

Usage:
  ecos [-n] [-r] COLOR1 STRING1 [COLOR2 STRING2 …]
  ecos -s COLOR1 [COLOR2 …]
  ecos -e COLOR1 [COLOR2 …]
  ecos -f COLOR1 RX1 ADDRESS1 COUNT1 [COLOR2 RX2 ADDRESS2 COUNT2 …] < <(foo)
  ecos -o COLOR1 [STRING1]
  ecos -O
  ecos -u [UTIL-OPTIONS]
  ecos --complete
  ecos -(h|H)

Options:
  | Flag | Description                                                      |
  |------+------------------------------------------------------------------|
  |      | regular mode: interleave colors and strings                      |
  | -n   | - and suppress trailing newline                                  |
  | -r   | - and reset colors and effects at each step (no inheritance)     |
  |------+------------------------------------------------------------------|
  | -s   | self    mode: colorize colors with themselves                    |
  | -e   | effect  mode: showcase effects of colors                         |
  | -f   | filter  mode: colorize stdin using sed-like addresses and counts |
  | -o   | open    mode: start colors that next commands can inherit        |
  | -O   | close   mode: reset any open colors                              |
  |------+------------------------------------------------------------------|
  | -u   | utility mode: convert, display, and search colors                |
  |------+------------------------------------------------------------------|
  | -h   | help (all this, with some colors)                                |
  | -H   | help (all this, monochrome)                                      |
_____________________________________________________________________________

Regular:
  ecos [-n] [-r] COLOR1 STRING1 [COLOR2 STRING2 …]

Complete colors look like this: $effect$depth$color_$effect$depth$color

- That's $foreground_$background. Either can be omitted.

- An $effect is zero or more comma-separated numbers.
  There're many effects; the most common are 0 (reset) and 1 (bold).

- $depth is one of [/:.] for 4, 8, 24-bits respectively.

- $color format depends on the depth.
  -  4-bit colors: black, red, green, blue, yellow, cyan, magenta, white
  -  8-bit colors: a number from 0 to 255
  - 24-bit colors: three input options:
    - X11 color name: tomato1
    - Hex triplet:    #ff6347    (short hex is ok, too: #ace == #aaccee)
    - Dec triplet:    255.99.71

So this:
  ecos 1,3.sandybrown_.purple3 "Hello, world!"

outputs that string in bold+italic sandybrown on a purple3 background.
_____________________________________________________________________________

Self:
  ecos -s COLOR1 [COLOR2 …]

Just try:
  ecos -s 1/red 0/blue 1:208 3.#f00 .#c9fe02 1.#ff40a7 .200.27.140 1.orange1_.navy
_____________________________________________________________________________

Effect:
  ecos -e COLOR1 [COLOR2 …]

Just try:
  ecos -e .skyblue1 /green :200
_____________________________________________________________________________

Filter:
  ecos -f COLOR1 RX1 ADDRESS1 COUNT1 [COLOR2 RX2 ADDRESS2 COUNT2 …] < <(foo)

where
  address: 3  1-5  3,7  2~2  /^# / etc.  (Use '.' to replace on every line)
    count: number of replacements        (Use '.' to replace all in the line)

It's a simple filter to add colors to text.

If you're familiar with sed syntax, this is what the filter does:
  ecos -f /red foob.. 2-3 4    <=>    sed -E "2-3 s/foob../${RED}&${NONE}/4"

where RED and NONE are color escape sequences wrappers around "foob.."

So this:
- colorizes the 4th match,
- of the regular expression "foob.." (which will match, e.g., "foobar"),
- on lines 2 and 3,
- of input coming from stdin.

Just try:
  ecos -f .green a . . .red p . 2 .cyan p . 3 .blue e . . <<<"apple banana grape"
  ecos -f 1/red 1 . . i/green 2 . . 1/yellow 5 . . 1/blue . 4 . < <(seq 5 20)
  ecos -f 1/red ^. /7$/ .                                       < <(seq 5 18)
_____________________________________________________________________________

Open:
  ecos -o COLOR1 [STRING1]

Close:
  ecos -O

Just try:
  ecos -o 1,3.orange Hello
  printf " there, "
  ecos -O
  echo dear World.
_____________________________________________________________________________

Utilities:
  ecos -u [UTIL-OPTIONS]

To read its own help, simply:
  ecos -u
_____________________________________________________________________________

Completion:

To enable it, add this line to your ~/.bash_aliases or ~/.bashrc:
  . <(ecos --complete)

or, if ecos executable is not in your PATH and hasn't been sourced:
  . <(/path/to/ecos --complete)

and then, when writing an ecos command, press <tab><tab> to show completions.

Note that your interactive shell must be Bash for completions to work.
_____________________________________________________________________________

Sourcing:

If you intend to use ecos in a Bash file that'll call it repeatedly, you may
want to source the file instead of executing it. There's usually a gain in
speed. For example, the examples-ecos file seems to run about 30% faster now
that it's sourcing ecos.

Add something like this to the file that'll source it:
  . /path/to/ecos || . ecos

It'll look for the file ecos in dir /path/to/, and if it doesn't find it
(maybe you end up moving it?) it'll try to find ecos somewhere in your PATH.
Or you could put just the second part, if you're sure it'll be in your PATH.
_____________________________________________________________________________

Read:  https://en.wikipedia.org/wiki/X11_color_names

       https://en.wikipedia.org/wiki/ANSI_escape_code
       See sections "SGR (Select Graphic Rendition) parameters" and "Colors".

       man 4 console_codes
       See section "ECMA-48 Select Graphic Rendition".
_____________________________________________________________________________

_
  LC_ALL=C.utf8
  [[ "$1" ]] || return 0
  hash getopt grep sed xargs || return 127

  local mod nnl rst  # mode nonewline reset
  opt=$(getopt -n ecos -l 'complete' -o 'nrfoOseuh::H::=:' -- "$@")
  eval set -- "$opt"

  while :; do
      case "$1" in
          -n) mod=norm nnl=true      ;;
          -r) mod=norm rst=true      ;;
          -f) mod=filt               ;;
          -o) mod=open               ;;
          -O) mod=clos               ;;
          -s) mod=self               ;;
          -e) mod=effc               ;;
          -u) mod=util; shift 2      ; break       ;;
          -h) ecos--doc     "$2"     ; return "$?" ;;
          -H) ecos--doc-nc  "$2"     ; return "$?" ;;
          -=) ecos--"$2"    "${@:4}" ; return "$?" ;;
          --complete) ecos--complete ; return "$?" ;;
          --) shift                  ; break       ;;
          * ) echo >&2 "Error"       ; return 1    ;;
      esac;   shift                  ; continue
  done

  case "${mod:=norm}" in
      norm) if  "${rst:=false}"
            then ecos--parse-pairs-with-reset "$@"
            else ecos--parse-pairs            "$@"
            fi; "${nnl:=false}" || echo ;;
      filt) ecos--filter       "$@" ;;
      effc) ecos--parse-effect "$@" ;;
      self) ecos--parse-self   "$@" ;;
      open) ecos--parse-pair   "$@" ;; # "$2" is optional
      clos) ecos--parse-pair 0 ""   ;;
      util) ecos--utils        "$@" ;;
  esac ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
#### Internal
##### Filter

ecos--filter()
{ : cclr1 rx1 address1 count1 _cclr2 _rx2 _address2 _count2 … <<_
A simple filter to add colors to text.

See ecos -h for more information.
_
  local sc="" co rx cn ad  # sed command, color, regex, count, address
  while (("$#">0)); do
      co="$(ecos--parse-completecolor "$1")" rx="$2"
      if [[ "$3" == "." ]]; then ad=""; else ad="$3"; fi
      if [[ "$4" == "." ]]; then cn=g;  else cn="$4"; fi
      if [[    "$co"    ]]; then sc+="$ad s/$rx/$co&\e[0m/$cn;"; fi
      shift 4
  done;: "${sc@E}"; sed -E "${_:-N}" ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
##### Parse
###### Pairs

ecos--parse-effect()
{ : color1 _color2 _color3 … <<_
Show application of common effects to user-provided colors, space-separated.
_
  shopt -s extglob; local ef co
  for ef in 0 1 2 3 4 5 7 9 53 21
  do for co; do ecos--parse-self "$ef${co/#+([0-9,])/}"; done
  done | xargs -n"$#" |
      if hash column 2>/dev/null; then column -t; else sed 's/ /  /g'; fi
  shopt -u extglob ;}

ecos--parse-self()
{ : completecolor1 _completecolor2 _completecolor3 … <<_
Colorize user-provided colors with themselves, space-separated.
Color is reset before each and at its end.
_
  ecos--reset
  while (("$#")); do ecos--parse-pair "$1" "$1"
                     ecos--reset " "; shift; done | sed '$ s/ $/\n/' ;}

ecos--parse-pairs-with-reset()
{ : completecolor1 string1 _completecolor2 _string2 … <<_
Parse user-provided pairs with ecos--parse-pair, which see.
Color is reset before each and at its end. Return colorized string.
_
  while (("$#">=2)); do ecos--reset
                        ecos--parse-pair "$1" "$2"; shift 2; done; ecos--reset ;}

ecos--parse-pairs()
{ : completecolor1 string1 _completecolor2 _string2 … <<_
Parse user-provided pairs with ecos--parse-pair, which see.
Color is reset at its end. Return colorized string.
_
  while (("$#">=2)); do ecos--parse-pair "$1" "$2"; shift 2; done; ecos--reset ;}

ecos--parse-pair()
{ : completecolor string <<_
Parse user-provided pair of COMPLETECOLOR plus STRING.
Return colorized string. Color is not reset at its end.
_
  printf -- '%s%s' "$(ecos--parse-completecolor "$1")" "${2@E}" ;}

ecos--reset()
{ : _string <<_
Insert sequence to reset effects. Optionally add STRING.
Previous effects will then no longer be inherited by next items.
_
  printf -- "\e[0m%s" "$1" ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
###### Colors

ecos--parse-completecolor()
{ : completecolor <<_
Parse user-provided COMPLETECOLOR.
COMPLETECOLOR includes foreground and/or background, each of which
can include optional effect and/or intensity.
Return escape sequence.
_
  local rx="([,0-9]*)(i*)([:/.]?)([^_]*)"    {e,i,d,c,p}{f,b}
  if [[ "$1" =~ ^($rx)?(_$rx)?$ ]]; then
      ef="${BASH_REMATCH[2]//,/;}"         # [e]ffect    [f]oreground
      if="${BASH_REMATCH[3]}"              # [i]ntensity
      df="${BASH_REMATCH[4]}"              # [d]epth
      cf="${BASH_REMATCH[5]}"              # [c]olor
      eb="${BASH_REMATCH[7]//,/;}"         # [e]ffect    [b]ackground
      ib="${BASH_REMATCH[8]}"              # [i]ntensity
      db="${BASH_REMATCH[9]}"              # [d]epth
      cb="${BASH_REMATCH[10]}"             # [c]olor
      case  "$df" in [.]): 24;; /): 4;; :): 8;; esac;      df="$_"
      case  "$db" in [.]): 24;; /): 4;; :): 8;; esac;      db="$_"
      [[ -z "$cf"  ]] && pf=""   ||     pf=$(ecos--parse-"$df" "$cf")
      [[ -z "$cb"  ]] && pb=""   ||     pb=$(ecos--parse-"$db" "$cb")
      [[ -z "$if"  ]] && if=0    ||     if=6 # 3x→ 9x (e.g. 31m→ 91m)
      [[ -z "$ib"  ]] && ib=0    ||     ib=6 # 4x→10x (e.g. 42m→102m)
      [[ "${ef%;}" ]] && ef+=";" ||:
      [[ "${eb%;}" ]] && eb+=";" ||:
      ((df==4)) && : "$if" || : 0; [[ "$pf" ]] && pf="$((3+_))$pf"||:
      ((db==4)) && : "$ib" || : 0; [[ "$pb" ]] && pb="$((4+_))$pb"||:
      : "$ef$pf$eb$pb"; else  : 39; fi; printf -- '\e[%sm' "${_%;}" ;}

ecos--parse-() { :;}  # Ignore color if no depth marker is passed.

ecos--parse-4()
{ : color <<_
Parse user-provided stripped COLOR, type 4 bits.
_
  case "$1" in
      [0-9]): "$1";;
      black): 0;; red    ): 1;; green): 2;; yellow): 3;;
      blue ): 4;; magenta): 5;; cyan ): 6;; white ): 7;; *): 9;;
  esac; printf "%s;" "$_" ;}

ecos--parse-8()
{ : color <<_
Parse user-provided stripped COLOR, type 8 bits.
_
  if [[ "$1" =~ ^[0-9]+$ ]]
  then printf "8;5;%s;" "$1"
  else printf "9;"
  fi ;}

ecos--parse-24()
{ : color <<_
Parse user-provided stripped COLOR, type 24 bits.
_
  if   [[ "$1" =~ ^[0-9A-Za-z]+$ ]]
  then ecos--nam2dec "${BASH_REMATCH[0],,}"
  elif [[ "$1" =~ ^#([0-9A-Fa-f]+)$ ]]
  then ecos--hex2dec "${BASH_REMATCH[1],,}"
  elif [[ "$1" =~ ^([0-9]{1,3})[.:\;]([0-9]{1,3})[.:\;]([0-9]{1,3})$ ]]
  then printf "%s;%s;%s" "${BASH_REMATCH[@]:1:3}"
  else true
  fi |
      if res="$(</dev/stdin)"
         grep -q ';'    <<< "$res"
      then printf "8;2;%s;" "$res"
      else printf "9;"
      fi ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
##### Color utilities
###### Select utility

ecos--utils()
{ : fun args <<_
Convert, list, display, or search colors.

Usage:
  ecos -u FUN ARGS

Convert:
  FUN can be one of these conversion functions:
  - h2d = from hex to dec
  - d2h = from dec to hex
  - n2d = from nam to dec
  - n2h = from nam to hex
  - d2n = from dec to nam
  - h2n = from hex to nam

    whose ARGS are, depending on FUN, one or more colors as:
    - hexadecimal (ff0000 or f00)
    - decimal     (255.0.0)
    - name        (red)

List:
  FUN can also be one of these name-listing functions:
  - lnd  = color names as decimals
  - lnh  = color names as hex
  - lnhd = color names as decimals and hex

    In this case, ARG is an optional field delimiter.
    If omitted, colon is used. Otherwise:
    - t makes it tab-separated
    - s makes it space-separated (columns)
    - any other character (e.g., a comma) is used literally as delimiter.

Display:
  FUN can also be this name-display function:
  - lnc = color names in their colors

    Optional 1st ARG is a regular expression: filter what colors to show.
    Optional 2nd ARG is an integer: show only names, in that many columns.

Search:
  FUN can also be this search function:
  - find = show all info available on named colors that match this regex

    Here, ARG is a regular expression.
    It can be part of a color name, an hex, or a decimal. Try:
      ecos -u find ff1    # will match hex and a name
      ecos -u find 42     # will match dec, hex, and a name

Pageless:
  When FUN is ln* or find, the results are paged with less.
  To remove paging, just pipe it to cat:
      ecos -u lnc o.2 3 | cat
_
  case "$1" in
      (d2h)  ecos--dec2hex "${@:2}" ;;
      (h2d)  ecos--hex2dec "${@:2}" ;;
      (d2n)  ecos--dec2nam "${@:2}" ;;
      (h2n)  ecos--hex2nam "${@:2}" ;;
      (n2d)  ecos--nam2dec "${@:2}" ;;
      (n2h)  ecos--nam2hex "${@:2}" ;;
      (lnc)  ecos--nam-col "${@:2}" ;;
      (lnd)  ecos--nam-dec "${@:2}" ;;
      (lnh)  ecos--nam-hex "${@:2}" ;;
      (lnhd) ecos--nam-h-d "${@:2}" ;;
      (find) ecos--find-rx "${@:2}" ;;
      (*  )  ecos--doc utils        ;;
  esac ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
###### Convert between color representation
####### Between hex and decimal triplets

ecos--dec2hex()
{ : dec1 _dec2 _dec3 … <<_
Convert decimal triplets to lowercase hex triplets.
The triplet(s) can be separated by dots, colons, or semicolons.
_
  for color; do
      if [[ "$color" =~ ^([0-9]{1,3})[.:\;]([0-9]{1,3})[.:\;]([0-9]{1,3})$ ]]
      then declare   r g b
           read -r _ r g b <<< "${BASH_REMATCH[*]}"
           printf "%02x%02x%02x\n" "$r" "$g" "$b"
      else printf "\n"
      fi
  done ;}

ecos--hex2dec()
{ : hex1 _hex2 _hex3 … <<_
Convert lowercase HEX triplets to semicolon-separated decimal triplets.
_
  for color; do
      if   [[ "$color" =~ ^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$ ]]
      then declare   r g b
           read -r _ r g b <<< "${BASH_REMATCH[*]}"
           ((r=0x"$r"  ,  g=0x"$g"  ,  b=0x"$b"  ,  1))
           printf "%s;%s;%s\n" "$r" "$g" "$b"
      elif [[ "$color" =~ ^([0-9a-f]{1})([0-9a-f]{1})([0-9a-f]{1})$ ]]
      then declare   r g b
           read -r _ r g b <<< "${BASH_REMATCH[*]}"
           ((r=0x"$r$r",  g=0x"$g$g",  b=0x"$b$b",  1))
           printf "%s;%s;%s\n" "$r" "$g" "$b"
      else printf "\n"
      fi
  done ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
####### To names from hex and decimal triplets

ecos--dec2nam()
{ : dec1 _dec2 _dec3 <<_
Convert decimal triplets to color names.
The triplets can be separated by dots, colons, or semicolons.
_
  for color; do
      ecos--nam-dec-raw | grep -P ":${color//[.;]/:}$" | cut -f1 -d: | xargs
  done ;}

ecos--hex2nam()
{ : hex1 _hex2 _hex3 … <<_
Convert lowercased HEX triplets to color names.
_
  for color; do
      : "$(ecos--hex2dec "$color")"
      if [[ "$_" ]]; then ecos--dec2nam "$_"; fi
  done ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
####### From names to hex and decimal triplets

ecos--nam2dec()
{ : colorname1 _colorname2 _colorname3 … <<_
Convert COLORNAMEs to semicolon-separated decimal triplets.
_
  for color; do
      ecos--nam-dec-raw | grep -P "^${color//grey/gray}:" |
          cut -f2- -d:  | tr : \;
  done ;}

ecos--nam2hex()
{ : colorname1 _colorname2 _colorname3 … <<_
Convert COLORNAMEs to lowercased hex triplets.
_
  for color; do
      : "$(ecos--nam2dec "$color")"
      if [[ "$_" ]]; then ecos--dec2hex "$_"; fi
  done ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
###### Display color information
####### Display formatted data

ecos--nam-col()
{ : _regex _ncols <<_
Output color names in their own colors beside a background of the same.
Optional first argument is a regular expression: filter what colors to show.
Optional second argument is an integer: show only names, in that many columns.
_
  local rx="${1:-.}" n="$2"
  ecos--nam-dec-raw |
      if [[ "$rx" == "." ]]
      then cat
      else cut -f1 -d: | grep -P "$rx" | (: "^($(tr '\n' '|')):"
                                          ecos--nam-dec-raw | grep -P "$_")
      fi | ecos--colorize-colors "$n" |
      if [[ "$n" =~ ^[0-9]+$ ]] && (("$n">1))
      then xargs -n"$n" |
              if hash column 2>/dev/null
              then column -t; else sed "s/ /  /g"; fi
      else cat
      fi | \less -R ;}

ecos--nam-dec()
{ : _delimiter <<_
Output data with four fields: COLORNAME R G B
COLORNAME is lowercased, no spaces. R, G, B are decimals in 0–255 range.
DELIMITER, if omitted, is colon. Otherwise:
- t makes it tab-separated
- s makes it space-separated (columns)
- any other character (e.g., a comma) is used literally as delimiter.
_
  ecos--nam-dec-raw | ecos--change-delimiter "$1" | \less ;}

ecos--nam-hex()
{ : _delimiter <<_
Output data with two fields: COLORNAME HEXTRIPLET
COLORNAME is lowercased, no spaces. HEXTRIPLET is lowercased in 00–ff tange.
For the meaning of DELIMITER, see ecos--nam-dec.
_
  paste -d: \
        <(ecos--nam-dec-raw | cut -d: -f1) \
        <(mapfile -t < <(ecos--nam-dec-raw | cut -d: -f2-)
          ecos--dec2hex "${MAPFILE[@]}") |
      ecos--change-delimiter "$1" | \less ;}

ecos--nam-h-d()
{ : _delimiter <<_
Output data with five fields: COLORNAME HEXTRIPLET R G B
For the meaning of these variables, see ecos--nam-dec and ecos--nam-hex.
_
  join <(ecos--nam-hex) \
       <(ecos--nam-dec) -t: | ecos--change-delimiter "$1" | \less ;}

ecos--find-rx()
{ : regex <<_
Show all info available on colors that match REGEX.
REGEX can be part of a color name, an hex, or a decimal.
_
  local rx="$1"
  paste <(ecos--nam-h-d ';' | grep -P --color=none "$rx" |
              while IFS=$';' read -r _ _ rgb
              do : "\e[48;2;${rgb}m        \e[0m"
                 printf -- '%s\n' "${_@E}"
              done) <(ecos--nam-h-d | grep -P --color=none "$rx" |
                          ecos--change-delimiter s |
                          grep -P --color=always "$rx") |
      sed 's/\t/  /' | \less -x4 -R ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
####### Format raw data

ecos--colorize-colors()
{ : _nobg <<_
Filter to apply decimal RGB to color names beside a background of the same.
Passing anything as first argument shows only the names (no background).
_
  [[ "$1" ]] && : ""  || : "\x1b[48:2:\2m        \x1b[0m  "
  sed -E "s/^([^:]+):(.*)/$_\x1b[38:2:\2m\1\x1b[0m/ ; s/:/;/g" ;}

ecos--change-delimiter()
{ : _new-delimiter _old-delimiter <<_
Filter to change field delimiters.
_
  : "${2:-:}"  # three colons, three different meanings... :)
  case "$1" in
      "") cat                ;;
      s ) column -ts"$_"     ;;
      t ) tr "$_" '\t'       ;;
      * ) tr "$_" "${1:0:1}" ;;
  esac ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
####### Output raw data

ecos--nam-dec-raw()
{ : <<_
Output colon-delimited data with four fields: COLORNAME R G B
COLORNAME is lowercased, no spaces. R, G, B are decimals in 0–255 range.
_
  tr , "\n" <<<"\
aliceblue:240:248:255,antiquewhite1:255:239:219,antiquewhite2:238:223:204
antiquewhite3:205:192:176,antiquewhite4:139:131:120,antiquewhite:250:235:215
aquamarine1:127:255:212,aquamarine2:118:238:198,aquamarine3:102:205:170
aquamarine4:69:139:116,aquamarine:127:255:212,azure1:240:255:255
azure2:224:238:238,azure3:193:205:205,azure4:131:139:139,azure:240:255:255
beige:245:245:220,bisque1:255:228:196,bisque2:238:213:183,bisque3:205:183:158
bisque4:139:125:107,bisque:255:228:196,black:0:0:0,blanchedalmond:255:235:205
blue1:0:0:255,blue2:0:0:238,blue3:0:0:205,blue4:0:0:139,blueviolet:138:43:226
blue:0:0:255,brown1:255:64:64,brown2:238:59:59,brown3:205:51:51,brown4:139:35:35
brown:165:42:42,burlywood1:255:211:155,burlywood2:238:197:145
burlywood3:205:170:125,burlywood4:139:115:85,burlywood:222:184:135
cadetblue1:152:245:255,cadetblue2:142:229:238,cadetblue3:122:197:205
cadetblue4:83:134:139,cadetblue:95:158:160,chartreuse1:127:255:0
chartreuse2:118:238:0,chartreuse3:102:205:0,chartreuse4:69:139:0
chartreuse:127:255:0,chocolate1:255:127:36,chocolate2:238:118:33
chocolate3:205:102:29,chocolate4:139:69:19,chocolate:210:105:30
coral1:255:114:86,coral2:238:106:80,coral3:205:91:69,coral4:139:62:47
coral:255:127:80,cornflowerblue:100:149:237,cornsilk1:255:248:220
cornsilk2:238:232:205,cornsilk3:205:200:177,cornsilk4:139:136:120
cornsilk:255:248:220,cyan1:0:255:255,cyan2:0:238:238,cyan3:0:205:205
cyan4:0:139:139,cyan:0:255:255,darkblue:0:0:139,darkcyan:0:139:139
darkgoldenrod1:255:185:15,darkgoldenrod2:238:173:14,darkgoldenrod3:205:149:12
darkgoldenrod4:139:101:8,darkgoldenrod:184:134:11,darkgray:169:169:169
darkgreen:0:100:0,darkkhaki:189:183:107,darkmagenta:139:0:139
darkolivegreen1:202:255:112,darkolivegreen2:188:238:104
darkolivegreen3:162:205:90,darkolivegreen4:110:139:61,darkolivegreen:85:107:47
darkorange1:255:127:0,darkorange2:238:118:0,darkorange3:205:102:0
darkorange4:139:69:0,darkorange:255:140:0,darkorchid1:191:62:255
darkorchid2:178:58:238,darkorchid3:154:50:205,darkorchid4:104:34:139
darkorchid:153:50:204,darkred:139:0:0,darksalmon:233:150:122
darkseagreen1:193:255:193,darkseagreen2:180:238:180,darkseagreen3:155:205:155
darkseagreen4:105:139:105,darkseagreen:143:188:143,darkslateblue:72:61:139
darkslategray1:151:255:255,darkslategray2:141:238:238,darkslategray3:121:205:205
darkslategray4:82:139:139,darkslategray:47:79:79,darkturquoise:0:206:209
darkviolet:148:0:211,deeppink1:255:20:147,deeppink2:238:18:137
deeppink3:205:16:118,deeppink4:139:10:80,deeppink:255:20:147
deepskyblue1:0:191:255,deepskyblue2:0:178:238,deepskyblue3:0:154:205
deepskyblue4:0:104:139,deepskyblue:0:191:255,dimgray:105:105:105
dodgerblue1:30:144:255,dodgerblue2:28:134:238,dodgerblue3:24:116:205
dodgerblue4:16:78:139,dodgerblue:30:144:255,firebrick1:255:48:48
firebrick2:238:44:44,firebrick3:205:38:38,firebrick4:139:26:26
firebrick:178:34:34,floralwhite:255:250:240,forestgreen:34:139:34
gainsboro:220:220:220,ghostwhite:248:248:255,gold1:255:215:0,gold2:238:201:0
gold3:205:173:0,gold4:139:117:0,goldenrod1:255:193:37,goldenrod2:238:180:34
goldenrod3:205:155:29,goldenrod4:139:105:20,goldenrod:218:165:32,gold:255:215:0
gray0:0:0:0,gray1:3:3:3,gray2:5:5:5,gray3:8:8:8,gray4:10:10:10,gray5:13:13:13
gray6:15:15:15,gray7:18:18:18,gray8:20:20:20,gray9:23:23:23,gray10:26:26:26
gray11:28:28:28,gray12:31:31:31,gray13:33:33:33,gray14:36:36:36,gray15:38:38:38
gray16:41:41:41,gray17:43:43:43,gray18:46:46:46,gray19:48:48:48,gray20:51:51:51
gray21:54:54:54,gray22:56:56:56,gray23:59:59:59,gray24:61:61:61,gray25:64:64:64
gray26:66:66:66,gray27:69:69:69,gray28:71:71:71,gray29:74:74:74,gray30:77:77:77
gray31:79:79:79,gray32:82:82:82,gray33:84:84:84,gray34:87:87:87,gray35:89:89:89
gray36:92:92:92,gray37:94:94:94,gray38:97:97:97,gray39:99:99:99
gray40:102:102:102,gray41:105:105:105,gray42:107:107:107,gray43:110:110:110
gray44:112:112:112,gray45:115:115:115,gray46:117:117:117,gray47:120:120:120
gray48:122:122:122,gray49:125:125:125,gray50:127:127:127,gray51:130:130:130
gray52:133:133:133,gray53:135:135:135,gray54:138:138:138,gray55:140:140:140
gray56:143:143:143,gray57:145:145:145,gray58:148:148:148,gray59:150:150:150
gray60:153:153:153,gray61:156:156:156,gray62:158:158:158,gray63:161:161:161
gray64:163:163:163,gray65:166:166:166,gray66:168:168:168,gray67:171:171:171
gray68:173:173:173,gray69:176:176:176,gray70:179:179:179,gray71:181:181:181
gray72:184:184:184,gray73:186:186:186,gray74:189:189:189,gray75:191:191:191
gray76:194:194:194,gray77:196:196:196,gray78:199:199:199,gray79:201:201:201
gray80:204:204:204,gray81:207:207:207,gray82:209:209:209,gray83:212:212:212
gray84:214:214:214,gray85:217:217:217,gray86:219:219:219,gray87:222:222:222
gray88:224:224:224,gray89:227:227:227,gray90:229:229:229,gray91:232:232:232
gray92:235:235:235,gray93:237:237:237,gray94:240:240:240,gray95:242:242:242
gray96:245:245:245,gray97:247:247:247,gray98:250:250:250,gray99:252:252:252
gray100:255:255:255,gray:190:190:190,green1:0:255:0,green2:0:238:0
green3:0:205:0,green4:0:139:0,greenyellow:173:255:47,green:0:255:0
honeydew1:240:255:240,honeydew2:224:238:224,honeydew3:193:205:193
honeydew4:131:139:131,honeydew:240:255:240,hotpink1:255:110:180
hotpink2:238:106:167,hotpink3:205:96:144,hotpink4:139:58:98,hotpink:255:105:180
indianred1:255:106:106,indianred2:238:99:99,indianred3:205:85:85
indianred4:139:58:58,indianred:205:92:92,ivory1:255:255:240,ivory2:238:238:224
ivory3:205:205:193,ivory4:139:139:131,ivory:255:255:240,khaki1:255:246:143
khaki2:238:230:133,khaki3:205:198:115,khaki4:139:134:78,khaki:240:230:140
lavenderblush1:255:240:245,lavenderblush2:238:224:229,lavenderblush3:205:193:197
lavenderblush4:139:131:134,lavenderblush:255:240:245,lavender:230:230:250
lawngreen:124:252:0,lemonchiffon1:255:250:205,lemonchiffon2:238:233:191
lemonchiffon3:205:201:165,lemonchiffon4:139:137:112,lemonchiffon:255:250:205
lightblue1:191:239:255,lightblue2:178:223:238,lightblue3:154:192:205
lightblue4:104:131:139,lightblue:173:216:230,lightcoral:240:128:128
lightcyan1:224:255:255,lightcyan2:209:238:238,lightcyan3:180:205:205
lightcyan4:122:139:139,lightcyan:224:255:255,lightgoldenrod1:255:236:139
lightgoldenrod2:238:220:130,lightgoldenrod3:205:190:112
lightgoldenrod4:139:129:76,lightgoldenrodyellow:250:250:210
lightgoldenrod:238:221:130,lightgray:211:211:211,lightgreen:144:238:144
lightpink1:255:174:185,lightpink2:238:162:173,lightpink3:205:140:149
lightpink4:139:95:101,lightpink:255:182:193,lightsalmon1:255:160:122
lightsalmon2:238:149:114,lightsalmon3:205:129:98,lightsalmon4:139:87:66
lightsalmon:255:160:122,lightseagreen:32:178:170,lightskyblue1:176:226:255
lightskyblue2:164:211:238,lightskyblue3:141:182:205,lightskyblue4:96:123:139
lightskyblue:135:206:250,lightslateblue:132:112:255,lightslategray:119:136:153
lightsteelblue1:202:225:255,lightsteelblue2:188:210:238
lightsteelblue3:162:181:205,lightsteelblue4:110:123:139
lightsteelblue:176:196:222,lightyellow1:255:255:224,lightyellow2:238:238:209
lightyellow3:205:205:180,lightyellow4:139:139:122,lightyellow:255:255:224
limegreen:50:205:50,linen:250:240:230,magenta1:255:0:255,magenta2:238:0:238
magenta3:205:0:205,magenta4:139:0:139,magenta:255:0:255,maroon1:255:52:179
maroon2:238:48:167,maroon3:205:41:144,maroon4:139:28:98,maroon:176:48:96
mediumaquamarine:102:205:170,mediumblue:0:0:205,mediumorchid1:224:102:255
mediumorchid2:209:95:238,mediumorchid3:180:82:205,mediumorchid4:122:55:139
mediumorchid:186:85:211,mediumpurple1:171:130:255,mediumpurple2:159:121:238
mediumpurple3:137:104:205,mediumpurple4:93:71:139,mediumpurple:147:112:219
mediumseagreen:60:179:113,mediumslateblue:123:104:238
mediumspringgreen:0:250:154,mediumturquoise:72:209:204
mediumvioletred:199:21:133,midnightblue:25:25:112,mintcream:245:255:250
mistyrose1:255:228:225,mistyrose2:238:213:210,mistyrose3:205:183:181
mistyrose4:139:125:123,mistyrose:255:228:225,moccasin:255:228:181
navajowhite1:255:222:173,navajowhite2:238:207:161,navajowhite3:205:179:139
navajowhite4:139:121:94,navajowhite:255:222:173,navy:0:0:128,oldlace:253:245:230
olivedrab1:192:255:62,olivedrab2:179:238:58,olivedrab3:154:205:50
olivedrab4:105:139:34,olivedrab:107:142:35,orange1:255:165:0,orange2:238:154:0
orange3:205:133:0,orange4:139:90:0,orangered1:255:69:0,orangered2:238:64:0
orangered3:205:55:0,orangered4:139:37:0,orangered:255:69:0,orange:255:165:0
orchid1:255:131:250,orchid2:238:122:233,orchid3:205:105:201,orchid4:139:71:137
orchid:218:112:214,palegoldenrod:238:232:170,palegreen1:154:255:154
palegreen2:144:238:144,palegreen3:124:205:124,palegreen4:84:139:84
palegreen:152:251:152,paleturquoise1:187:255:255,paleturquoise2:174:238:238
paleturquoise3:150:205:205,paleturquoise4:102:139:139,paleturquoise:175:238:238
palevioletred1:255:130:171,palevioletred2:238:121:159,palevioletred3:205:104:137
palevioletred4:139:71:93,palevioletred:219:112:147,papayawhip:255:239:213
peachpuff1:255:218:185,peachpuff2:238:203:173,peachpuff3:205:175:149
peachpuff4:139:119:101,peachpuff:255:218:185,peru:205:133:63,pink1:255:181:197
pink2:238:169:184,pink3:205:145:158,pink4:139:99:108,pink:255:192:203
plum1:255:187:255,plum2:238:174:238,plum3:205:150:205,plum4:139:102:139
plum:221:160:221,powderblue:176:224:230,purple1:155:48:255,purple2:145:44:238
purple3:125:38:205,purple4:85:26:139,purple:160:32:240,red1:255:0:0,red2:238:0:0
red3:205:0:0,red4:139:0:0,red:255:0:0,rosybrown1:255:193:193
rosybrown2:238:180:180,rosybrown3:205:155:155,rosybrown4:139:105:105
rosybrown:188:143:143,royalblue1:72:118:255,royalblue2:67:110:238
royalblue3:58:95:205,royalblue4:39:64:139,royalblue:65:105:225
saddlebrown:139:69:19,salmon1:255:140:105,salmon2:238:130:98,salmon3:205:112:84
salmon4:139:76:57,salmon:250:128:114,sandybrown:244:164:96,seagreen1:84:255:159
seagreen2:78:238:148,seagreen3:67:205:128,seagreen4:46:139:87,seagreen:46:139:87
seashell1:255:245:238,seashell2:238:229:222,seashell3:205:197:191
seashell4:139:134:130,seashell:255:245:238,sienna1:255:130:71,sienna2:238:121:66
sienna3:205:104:57,sienna4:139:71:38,sienna:160:82:45,skyblue1:135:206:255
skyblue2:126:192:238,skyblue3:108:166:205,skyblue4:74:112:139
skyblue:135:206:235,slateblue1:131:111:255,slateblue2:122:103:238
slateblue3:105:89:205,slateblue4:71:60:139,slateblue:106:90:205
slategray1:198:226:255,slategray2:185:211:238,slategray3:159:182:205
slategray4:108:123:139,slategray:112:128:144,snow1:255:250:250,snow2:238:233:233
snow3:205:201:201,snow4:139:137:137,snow:255:250:250,springgreen1:0:255:127
springgreen2:0:238:118,springgreen3:0:205:102,springgreen4:0:139:69
springgreen:0:255:127,steelblue1:99:184:255,steelblue2:92:172:238
steelblue3:79:148:205,steelblue4:54:100:139,steelblue:70:130:180,tan1:255:165:79
tan2:238:154:73,tan3:205:133:63,tan4:139:90:43,tan:210:180:140
thistle1:255:225:255,thistle2:238:210:238,thistle3:205:181:205
thistle4:139:123:139,thistle:216:191:216,tomato1:255:99:71,tomato2:238:92:66
tomato3:205:79:57,tomato4:139:54:38,tomato:255:99:71,turquoise1:0:245:255
turquoise2:0:229:238,turquoise3:0:197:205,turquoise4:0:134:139
turquoise:64:224:208,violetred1:255:62:150,violetred2:238:58:140
violetred3:205:50:120,violetred4:139:34:82,violetred:208:32:144
violet:238:130:238,wheat1:255:231:186,wheat2:238:216:174,wheat3:205:186:150
wheat4:139:126:102,wheat:245:222:179,whitesmoke:245:245:245,white:255:255:255
yellow1:255:255:0,yellow2:238:238:0,yellow3:205:205:0,yellow4:139:139:0
yellowgreen:154:205:50,yellow:255:255:0" ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
##### Enable completion

ecos--complete()
{ : <<_
Output commands necessary to enable completion.
_
  type _ecos | sed 1d
  echo "complete -o bashdefault -F _ecos ecos" ;}

_ecos()
{ : <<_
Enable completion for ecos.

Add this line to your ~/.bash_aliases or ~/.bashrc:
  . <(ecos --complete)

or, if ecos executable is not in your PATH and hasn't been sourced:
  . <(/path/to/ecos --complete)

and then, when writing an ecos command, press <tab><tab> to show completions.
_
  COMPREPLY=()
  local pro="$1" cur="$2" pre="$3" W=""     \
        eff="0 1 2 3 4 5 7 8 9 21 53"       \
        ops="-O -f -h -H -n -r -o -s -e -u" \
        dep="/ . :"

  case "$pre" in
      -h|-H)  : "" ;;
      -u)     : "help d2h h2d d2n h2n n2d n2h lnc lnd lnh lnhd find" ;;
      n2[dh]) : "$(ecos -=nam-dec-raw | cut -f1 -d:)" ;;
      find)   :         "REGEX" ;;
      lnc)    : "NOTHING REGEX" ;;
      ln*)    : "NOTHING t s ," ;;
      "$pro") : "$eff i $dep -" ;;
      -s)     : "$eff i $dep"   ;;
      -e)     :      "i $dep"   ;;
      *) if [[ "$pre" =~ _?[0-9,]*[0-9]$ || "$pre" =~ [/:.].*$ ]]
         then if   [[ "$COMP_LINE" =~ -s ]]; then : "$eff i $dep"
              elif [[ "$COMP_LINE" =~ -e ]]; then :      "i $dep"
              else                                : "STRING"
              fi # These ifs here, though not perfect, seem good enough.
         else                                     : "$eff i $dep"
         fi
  esac; mapfile -t COMPREPLY < <(compgen -W "$_" -- "$cur")

  W=$(# Completion words
      if   [[ "$cur" =~ ^[0-9,]*[0-9]$ ]]                 # any effect
      then compgen -P "$cur"        -W ", i $dep"
      elif [[ "$cur" =~ ^[0-9,]*[0-9],$ ]]                # comma
      then compgen -P "$cur"        -W "$eff"
      elif [[ "$cur" =~ ^[0-9,]*i$  ||
              "$cur" =~ ^[0-9,]*i?/ ]]                    #  4-bit
      then compgen -P "${cur%%/*}/" -W "black red green yellow
                                        blue magenta cyan white"
      elif [[ "$cur" =~ ^[0-9,]*: ]]                      #  8-bit
      then compgen -P "${cur%%:*}:" -W "$(seq -w 0 255)"
      elif [[ "$cur" =~ ^[0-9,]*[.] ]]                    # 24-bit
      then compgen -P "${cur%%.*}." -W "$(ecos -=nam-dec-raw|cut -f1 -d:)"
      elif [[ "${cur: -1}" == "_" ]]                      # background
      then compgen -P "$cur"        -W "$eff i $dep"
      elif [[ "${cur:0:2}" =~ -[Hh] ]]                    # -[Hh]FUN (dev opt)
      then compgen -P "${cur:0:2}"  -W "$(ecos -=funs-list)"
           printf -- "%s\n" "${cur:0:2}"
      elif [[ "${cur:0:1}" == "-" ]]                      # any options
      then compgen                  -W "$ops"
      fi)
   if [[ "$W" ]]; then mapfile -t COMPREPLY < <(compgen -W "$W" -- "$cur"); fi;}

ecos--funs-list()
{ : <<_
List internal functions that are completion candidates.
Remove common ecos-- prefix.
_
  : "${BASH_SOURCE[0]}"
  if [[ "$_" ]]
  then <"$_"        grep -oP '(?<=^ecos--).*(?=[(][)])'
  else declare -F | grep -oP '(?<= ecos--).*'
  fi ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
##### Get help

ecos--doc()
{ : _fun <<_
Show a function's docstring. Colors.
If FUN is empty, show the docstring of ecos.
Otherwise, show the docstring of ecos--FUN.
_
  ecos--doc-no-colors "$@" | ecos--doc-colorize | \less -RF ;}

ecos--doc-nc()
{ : _fun <<_
Show a function's docstring. No colors.
If FUN is empty, show the docstring of ecos.
Otherwise, show the docstring of ecos--FUN.
_
  ecos--doc-no-colors "$@" | \less -RF ;}

ecos--doc-no-colors()
{ : _fun <<_
Output to stdout a function's docstring without colors.
_
  : ecos"${1:+--$1}"; type "$_" | sed -n "/<<_$/,/^_$/p" |
      sed "1 {s/ <<_.*/)/ ; s/[^:]*: */$_ (/}; $ d" ;}

ecos--doc-colorize()
{ : <<_
Filter to colorize ecos' docstrings.
_
  ecos--filter 1.#fde028        '^[^ ]*'           1    .  \
               1.orange1        '\(.*'             1    .  \
               1.deepskyblue    '.*'               2    .  \
               1.#b4fa70        '^[A-Z][a-z]+:'    3,$  .  \
               0.lightskyblue   '^ *([|]|ecos).*'  .    .  \
               1.darkslateblue  '^_+$'             .    .  ;}

#⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
### Source it or run it

case "$-" in
    *i*) : "Presumably being sourced by interactive shell." ;;
    *  ) read -r REPLY _ < <(caller)
         if ((REPLY > 0))
         then : "Presumably being sourced by another file."
         else : "Presumably being executed."
              set -eo pipefail
              ecos "$@"
         fi
esac

# Local Variables:
# coding:                     utf-8
# indent-tabs-mode:           nil
# sentence-end-double-space:  nil
# outline-regexp:             "###* "
# End:

## Ecos ends here
📆 2026-W02-3📆 2026-01-07