1
\$\begingroup\$

Features:

  • messages formatted with clear borders
  • errors sent to stderr
  • colour handling
  • colours can be included in other strings echoed to the screen without having to use the -e option
  • the desired colour can be either a string or a function
  • colours can be issued as commands before echoing text

Limitations:

  • If the message is longer than the width of the terminal, the output will be strange
  • If the message contains newlines, the output will be strange
  • Non-colour formatting functions can be passed in
  • Non-colour strings can be passed in
  • The terminal is assumed to always use the default colours and resets back after each message

Questions:

  • Is there a simpler way to get a string of spaces the same length as the message passed in?
  • Is there a simpler way to get the output of a function if it exists, or a default value if it doesn't exist?
  • My preferred order of function execution is top-to-bottom, but BASH seems to do a single pass, so I've got bottom-to-top because my code immediately calls the functions above it. Is there a standard order of function declarations/definitions in BASH?

I'm very experienced in other languages, but I'm pretty new to BASH, so I could be making newbie mistakes. All feedback gratefully received! (unless it's mean!)

#!/bin/bash

fg_black="\e[30m"
fg_white="\e[37m"
bg_red="\e[41m"
bg_blue="\e[44m"
bg_purple="\e[45m"
black_on_red() { echo -en "$fg_black$bg_red"; }
white_on_blue() { echo -en "$fg_white$bg_blue"; }
black_on_purple() { echo -en "$fg_black$bg_purple"; }
reset_colours() { echo -en "\e[0m"; }

function_or_value() {
        func=$(declare -F "$1" 2>/dev/null)
        local value=$($func)
        echo -n "${value:-$1}"
}

colour_message() {
        local colour="$(function_or_value "$1")"
        local message=$2
        echo "$colour  ${message//[^ ]/ }  $(reset_colours)"
        echo "$colour  ${message}  $(reset_colours)"
        echo "$colour  ${message//[^ ]/ }  $(reset_colours)"
}

colour_error_message() {
        local colour=$1
        local message=$2
        colour_message >&2 "$colour" "$message"
}

colour_error_message "$(black_on_red)" "Error!!!"
colour_message white_on_blue "No Error!!!"

output of script

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$
fg_black="\e[30m"
fg_white="\e[37m"
bg_red="\e[41m"
bg_blue="\e[44m"
bg_purple="\e[45m"

These control codes are terminal-specific. Prefer to use tput to create the appropriate codes for the actual $TERM being used.

\$\endgroup\$
0
\$\begingroup\$

Make a string str full of spaces with the length of variable a:

printf -v str "%*s" ${#a} " "
# or
str="${a//[^ ]/ }*"

I don't like the feature that you can have 2 different kind of parameters (string or function name) for setting a color. In the future you might want to enhance your function (make it split up the sting to a fg and bd color, using _on_ te keep them apart) or limit the accepted color combinations.
However, if you really want to do so, you can try

[[ $(type -t "$1") == function ]] && "$1" || echo "$1"
# or
[[ "$1" =~ "_on_" ]]  && "$1" || echo "$1"

https://google.github.io/styleguide/shellguide.html#function-location suggests the order you use. I never use the suggestion of a main() fuction.
You might want to move all your functions to a different file that you can include with source.

\$\endgroup\$
4
  • \$\begingroup\$ That is my source file that I include in other files. The two lines at the bottom to use the colour messages would actually be in a different file. Why does the guide recommend having main() as the last function in the source? I've used C/C++ before and main() is always the first function. It seems that a programmer would read code from top to bottom, not bottom to top to find main(). \$\endgroup\$
    – CJ Dennis
    Commented Oct 9, 2021 at 22:49
  • \$\begingroup\$ Without main(): The compiler reads from top. When it finds code that needs to be executed, it will try before reading the rest of the file. Functions beneath that line will be unknown. So first all functions are given and on the end the code that calls the function. When I see a script, I loke for executable code at the bottom. This might be the reason that the main() code is at the bottom. \$\endgroup\$
    – Walter A
    Commented Oct 10, 2021 at 13:05
  • \$\begingroup\$ Another reason can be when the script is promoted to an include file. The call to main() at the bottom will be removed, and another script sourcing this mail can call main(). Now it is important to have main() defined after the other funtions. (I don't like above solution: it will fail when you have two include files both with a main() function). \$\endgroup\$
    – Walter A
    Commented Oct 10, 2021 at 13:06
  • \$\begingroup\$ Well, namespacing will be an issue for all languages that don't have namespacing. \$\endgroup\$
    – CJ Dennis
    Commented Oct 12, 2021 at 5:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.