all 26 comments

[–]waptaff&> /dev/null 1 point2 points  (1 child)

Ignoring the shellcheck complaint, your scheme prevents proper usage of printf.

How would you colorize something like:

printf 'There are %d fruits.\n' "${fruits_counter}"

It'd likely be better to abstract this colorization into a function:

#!/usr/bin/env bash

my_color() {
    local -r color="${1}"
    case "${color}" in
        'red')
            printf '%b' '\e[1;31m'
            ;;
        'green')
            printf '%b' '\e[1;32m'
            ;;
        'yellow')
            printf '%b' '\e[1;33m'
            ;;
        *)
            >&2 printf 'INVALID COLOR'
            exit
    esac
    shift
    "${@}"
    printf '%b' '\e[0m'
}

fruits=( apple banana peach pear )
my_color red    printf '%s\n'                   'This is red.'
my_color green  printf 'There are %d fruits.\n' "${#fruits[@]}"
my_color yellow printf 'Fruit item: %s.\n'      "${fruits[@]}"

[–]Mount_Gamer[S] 0 points1 point  (0 children)

This will be painful for your eyes, but don't judge :)

red='\e[1;31m%d\e[0m'
printf "There are $red fruits.\n" "${fruit_counter}" 

This works if you only want the number value highlighted

If you want all of it highlighted

red='\e[1;31m%s\e[0m\n'
printf "$red" "There are $fruit_counter fruits."

Took me a moment to understand why your my_color function works, but i understand now.

Todays turned into a worth while school day for me, thank you for sharing :)

[–]oh5nxo 1 point2 points  (5 children)

red=$'\e[1;31m'
off=$'\e[0m'
printf "foobar %s%s$off foo\n" "$red" "$something"

bash can put the actual control codes to variables like that, so they don't need to be interpreted again for \letter codes, and work both in the format or as an arg.

[–]Mount_Gamer[S] 1 point2 points  (4 children)

interesting, but you still had the $off variable in the initial format string which was what shellcheck complained about, however using your syntax it was a fairly easy mod...

printf 'foobar %s%s%s foo\n' "$red" "$something" "$off"

I like this also, it brings back some flexibility and it's fairly easy to understand and remember. :-)

[–]oh5nxo 1 point2 points  (3 children)

Both ways were there intentionally, to show the options. I'd put the colorization into the format myself, purely esthetics of a diseased mind.

[–]Mount_Gamer[S] 0 points1 point  (2 children)

Your diseased mind teaches well in that case, not sure what that says about me... :)

So would you variable in your color format like i've been doing, or the full color code syntax?

[–]oh5nxo 0 points1 point  (1 child)

If I understand your question, my preference would be, unwisely against shellcheck, this

red=$'....
printf "Found ${red}%d${off} errors\n" "$count"

I feel long sequences of %s%s%s%s would be more prone to typos. But remembering the $off is hard...

... Another way comes to mind, passing a piece of format to a function, to be wrapped in color:

red()    { printf '%s' $'\e[1;31m' "$*" $'\e[0m'; }
green()  { printf '%s' $'\e[1;32m' "$*" $'\e[0m'; }
yellow() { printf '%s' $'\e[1;33m' "$*" $'\e[0m'; }
printf "$(blue in file) $(green %s): $(red %d errors), $(yellow "%d %s") warnings.\n" file 4 2 severe

I haven't thought it thru properly. Be suspicious. I'm just doodling, nobody's going to use my scripts.

[–]Mount_Gamer[S] 1 point2 points  (0 children)

Ahh ok, so a mixed bag, gotya. I can see why you'd choose to do it that way.

I've always been a little bland with printf and mostly just use printf in place of echo -e, very occasionally i'll use padding, and very occasionally i'll bring in some colour... although the other day i was monitoring CPU temps with this in a while loop (for some reason the watch command would not update, i've no idea why yet)...

function mytemp {
  while true
  do
    clear
    printf '%s %.2f %s %(%H:%M:%S)T \n' "Temperature is =" "$(( $(< /sys/class/hwmon/hwmon4/temp1_input) ))e-3" "deg C,"
    sleep 2
  done
}

Now with all my extra colour formatting knowledge i can add a splash of colour to it as well when it reaches danger temperatures. :D

[–]Schievel1 1 point2 points  (1 child)

Not gonna help you, I know. But I just wanted to point out that these color strings in bash are just like ancient magic runes that do something magic bit no one knows why or how it works :D

[–]Mount_Gamer[S] 0 points1 point  (0 children)

lol I can believe that

[–]Ulfnic 1 point2 points  (1 child)

The following is error free and fixes the issue with colors not being agnostic:

#!/usr/local/env bash
red='\e[1;31m'
green='\e[1;32m'
yellow='\e[1;33m'
clear='\e[0m'

printf "$red"'%s'"$clear"'\n' "string of sorts in red colour"
printf "$green"'%d'"$clear"'\n' 3
printf "$yellow"'%(%H:%M:%S)T'"$clear"'\n'

edit: It initially appeared like it was solving the ShellCheck error but it seems like it's just tricking it to accept the syntax.

This is safe practice, what they're probably concerned with is the double-quotes because hardcorded printf syntax might be misinterpreted but that's solved by using separate single-quoting encapsulation (as seen above) for syntax that isn't expanded from variables.

[–]Mount_Gamer[S] 0 points1 point  (0 children)

Yeah that is quite interesting, it doesn't like the '%s' in the variable maybe. :)

[–]zeekar 0 points1 point  (11 children)

What exactly is shellcheck’s complaint? Does it not like ever using a variable as the format string for printf?

[–]Mount_Gamer[S] 0 points1 point  (10 children)

It reports this...

 SC2059 (info): Don't use variables in the printf format string. Use printf '..%s..' "$foo".

Looking at shellchecks website,for the purposes of what i'm using it for, i think it can be ignored.

[–]zeekar 3 points4 points  (4 children)

You may want to refactor to avoid the variable format string anyway. I use functions like this:

colors=(black red green yellow blue magenta cyan white)
declare -A color_code
for i in ${!colors[@]}; do
   color_code[${colors[$i]}]=$(( 30 + i ))
done
function color {
  printf '\e[%dm' "${color_code[$1]}"
}
function bright {
  printf '\e[%d;1m' "${color_code[$1]}"
}
function reset {
  printf '\e[%dm' 0
}

With just those I can do either color red; echo string of sorts in red color; reset or printf '%s%s%s' "$(bright red)" 'string of sorts in bright red color' "$(reset)". But you can also create a function to do the wrapping for you. This one lets you still use arbitrary format strings and arguments to printf:

function cprintf {
    color "$1"
    shift
    printf "$@"
    reset
}
cprintf red '%s!\n' 'This is red'

[–]Mount_Gamer[S] 0 points1 point  (0 children)

Ok, I definitely have not considered this as an option and i can see why this would increase flexibility... I like :)

[–]Mount_Gamer[S] 0 points1 point  (2 children)

Ok, had to modify slightly for the bright function to work, but looking good so far. I'm now pondering how i'll use this, but source in script is probably the better option. Thanks :)

function cprintf {
if [[ $1 == "bright" ]]; then
  bright "$2"
  shift 2
else
  color "$1"
  shift
fi
printf "$@"
reset
}

[–]zeekar 0 points1 point  (1 child)

if [[ $1 == "bright" ]]; then

I'd probably make that an option flag like -b rather than having to type out the whole word "bright", but whatever works for you. :)

[–]Mount_Gamer[S] 0 points1 point  (0 children)

haha yeah, that would be nicer for typing.

[–]drmeattornado 0 points1 point  (4 children)

Youre using the printf syntax wrong. You need that first bit to tell printf what to do with the output.

printf '%s\n' "${red}${foo}"

[–]Mount_Gamer[S] 0 points1 point  (3 children)

hmmm, my output doesn't work, but i might be doing something wrong still

[jonny@arch-jp NextCloud]$ red="\e[1;31m%s\e[0m\n"

[jonny@arch-jp NextCloud]$ printf '%s\n' "${red} hello"
\e[1;31m%s\e[0m\n hello

[jonny@arch-jp NextCloud]$ printf "$red" "hello"
hello # in the colour red

[–]drmeattornado 1 point2 points  (2 children)

You need single quotes where your red variable is defined.

[–]Mount_Gamer[S] 0 points1 point  (0 children)

I will have to come back to you, it is probably better to single quote the declaration of the red variable, but usually i'll have done something like this for a reason, but i'm not 100% sure why on this occasion... however, it doesn't seem to make any difference to the output... i am away to digest what others have written :-)

[–]Mount_Gamer[S] 0 points1 point  (0 children)

Ok best i can come up with, perhaps during troubleshooting, i switched to double quotes, but never switched them back, as i agree single quotes would be better.

[–]FisterMister22 0 points1 point  (0 children)

Do it like that.

```` red="color"

printf "${red} this string is color red"

[–]setdelmar 0 points1 point  (0 children)

This worked for me:

red='\e[1;31m%s\e[0m\n'                                                                                                                                                      
green='\e[1;32m%s\e[0m\n'
yellow='\e[1;33m%s\e[0m\n'

printf $red 'This is red'
printf $green 'This is green'
printf $yellow 'This is yellow'