fidian / ansi

ANSI escape codes in pure bash - change text color, position the cursor, much more

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:
demo_ansi_vs_colorsh
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 of echo -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.

image

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

image

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)"

image

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 be Bblack.
  • The start/stop colors are odd. It looks like the capitalization of Blink and blink 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.