Discussion on an alternative solution (mine)
Naereen opened this issue Β· comments
Hi,
I just discovered your project yesterday, and well done π it's well explained and appears to work exactly as advertised !
I have been using ANSI colors in bash scripts for years, with a completely different approach that I somewhat perceive as simpler and faster. Don't want to brag, not at all, I simply wanted to share it, in order to initiate a discussion: I'm curious to see the advantages and drawbacks of the two solutions.
ansi
Instead of a script that has to be called for each colored section, I use env variables. ansi
works like:
$ echo "French flag is $(ansi --blue blue), $(ansi --white white) and $(ansi --red red)."
French flag is blue, white, and red.
-
This opens three pipes, so it can be slow,-
The syntax is long, and repetitive (ansi --
is repeated),+
No need to remember about resetting to default or white after each colored part, so easy to use.
.color.sh
My approach also uses a script, but do not need to call it for each colored section.
The script is here: .color.sh
, and I source it in my bashrc. It simply defines variables that correspond to the ANSI color code. One for each color, background color etc.
To use it in a script, first I do:
[ -x "$HOME/.color.sh" ] && . "$HOME/.color.sh" # source color variables if available
And use it like this:
$ echo -e "French flag is ${blue}blue${white}, white, and ${red}red${white}."
French flag is blue, white, and red.
-
It relies on Bash env variables, which can be overridden in the script (even though it's easy not to, and the variables names are explicitly colors which usually are not use for this),-
It needs to have the.color.sh
script path known explicitly,-
Need to remember to do${white}
after every colored section,+
No pipe, so it is probably faster,+
Cleaner and shorter syntax,+
I also provide a.nocolor.sh
script, which simply dereferenced all the variables, so it is easy to add a cli option like--noANSI
or--color=none
to a script, and source.nocolor.sh
if it is given (see example here)
Screenshot
Here is a short illustration of these two examples:
I tested my approach on sh
, bash
, but not on fish
and zsh
even though it should work.
Discussion
So basically I would just like to hear your point of view @fidian on this discussion: do you agree with the -
drawbacks and +
advantages of both approaches ? Do you see others?
Many thanks in advance!
I think that you have basically summed up the differences. I'll do the same here from the other viewpoint.
Remembering
The biggest weakness in any computerized system is the human. Whenever humans are involved, things go awry.
.color.sh
requires the use ofecho -e
..color.sh
must use${reset}
or${nocolor}
to return the colors to normal.
Your examples typically say to do something like ${blue}blue${white} is good
, but the right closing should be ${blue}blue${nocolor} is good
.
Speed
You're certainly right about how much time subshells consume. ansi
is far slower. I'm working on a bench test program for speed comparisons. It isn't public yet (just like how the module system isn't finished and isn't public yet) but here's my testing script and the results.
#!/usr/bin/env bash
. bpm
bpm::include bench
. ./color.sh
bench::test::ansi() {
echo "French flag is $(ansi --blue blue), $(ansi --white white), and $(ansi --red red)." > /dev/null
}
bench::test::color-sh() {
echo -e "French flag is ${blue}blue${nocolor}, ${white}white${nocolor}, and ${red}red${nocolor}." > /dev/null
}
bench::auto
Features / Goals
-
ansi
lets you manipulate the cursor and perform several other commands..color.sh
focuses more around the display of text. -
ansi
can detect is ANSI colors are supported by the terminal..color.sh
just emits the color codes. -
ansi
lets you turn on and off individual colors and attributes..color.sh
tends to group them, such as bolding or dimming all colorized text.
Potential Problems
.color.sh
uses several variables. I could reuse them by accident in my functions, such as$nocolor
.
Personal Preference
.color.sh
uses bold by default.${red}
is bold,${Bred}
is dim. There's no way to get normal. You can see it with this command:echo "French flag is $(ansi --blue blue), $(ansi --white white) and $(ansi --red red).
echo -e "${red}bold red${reset}, ${Bred}dim red${reset}, $(ansi --red normal red)"
Critique on .color.sh
I thought I should throw this in because you could appreciate the feedback.
- Your shebang at the top should be
#!/usr/bin/env bash
so you can get Bash and you can get the version of Bash that a person prefers (the first one in their$PATH
). - When you source
.color.sh
, you use[[ -x "$HOME/.color.sh ]]
, which should be[[ -f ~/.color.sh ]]
because sourced files don't need to be executable and because$HOME
can be unset but Bash still knows where your home folder is when you use~
(and no quotes). - Sourcing
.color.sh
could be far simpler if you moved the file to within your$PATH
. Your line to include it could be. .color.sh || :
- Your example at the top of the file should use
${nocolor}
or${reset}
instead of${white}
to restore the original colors. - The explanation of
$B*
variables should say "dim" instead of "not bold". - Line 51,
Black
should beBblack
. - The start/stop colors are odd. It looks like the capitalization of
Blink
andblink
are backwards. Typically the capitalized one stops the color.
Summary
I only made ansi
to do occasional color codes in places. When I know I will be using a lot of colors, I simply capture the color codes to a string and use echo -e
myself, such as with Wick and the logging functions like wick-error
, wick-warn
, wick-info
, and wick-debug
Feel free to contact me directly or open further discussion issues. I think this one accomplished the goal you set out, so it's getting closed.
Hi,
Thanks for your quick feedback. I will correct the typo, thanks!
I agree with most of what you pointed out.
The choice of having bold by default and not dim was (years ago) intentional, as I preferred the way it looked (even though it is not "semantically" correct).
Another reason I used this .color.sh
approach based on variables is because I also developed and used heavily a Python module that uses the same approach (ansicolortags
).
In the Python community, all the main packages to use ANSI colors forced to use one color by line, like from colors import blue; blue("this line is blue!")
, and I thought that's stupid.
So for Python 2 and 3 I hacked a small script that does from ansicolortags import printc; printc("<blue>Blue<white> White<red> Red<reset>")
I should point out that originally I said that I can detect if the terminal supports ANSI. ansi
does not currently do that. It's a local modification I have that isn't working well so it isn't committed.
Similarly, if you want a speed improvement, you could just use ansi
to build the color codes. Of course, if you do this often it is better to just write a script to get the codes for you and skip calling ansi
because those calls to ansi
are terribly slow.
GREEN=$(ansi --green --no-restore)
RESET=$(ansi --reset-color)
echo -e "${GREEN}green${RESET}"
Thanks for telling me about .colors.sh
.
OK no issue.
I tried to implement a "battle-tested" test for support for ANSI colors on my Python library, and it appeared (5 years ago) to be too hard, so I didn't even thought on trying the same in Bash.
The .nocolor.sh
is left as a manual option to disabled the effect of .color.sh
.