printf escape code not formatting in Bash script

367 views Asked by At

I've recently started playing around with Bash, mostly using it to customize my .profile, and I've ran into an issue with the printf command.

In my scripts folder, I have an associative array set up for easy use of ANSI colour codes:

#!/usr/bin/env bash

declare -A fcolors=( 
[black]='\e[30m' 
[red]='\e[31m' 
[green]='\e[32m' 
[yellow]='\e[33m' 
[blue]='\e[34m' 
[magenta]='\e[35m' 
[cyan]='\e[36m' 
[gray]='\e[90m' 
# LIGHT COLOURS
[lgray]='\e[37m' 
[lred]='\e[91m' 
[lgreen]='\e[92m' 
[lyellow]='\e[93m' 
[lblue]='\e[94m' 
[lmagenta]='\e[95m' 
[lcyan]='\e[96m' 
[white]='\e[97m' 
[default]='\e[0m' )

I also, in yet another file, have a script to center output on the terminal, which, admittedly, I borrowed from someone's guide on GitHub:

#!/usr/bin/env bash

# original script by TrinityCoder

# USAGE: printcen "center text" "filler text"

function printcen {
        [[ $# == 0 ]] && return 1

        declare -i TERM_COLS="$(tput cols)"
        declare -i str_len="${#1}"
        [[ $str_len -ge $TERM_COLS ]] && {
                echo "$1";
                return 0;
        }

        declare -i filler_len="$(( (TERM_COLS - str_len) / 2 ))"
        [[ $# -ge 2 ]] && ch="${2:0:1}" || ch =" "
        filler=""
        for (( i = 0; i < filler_len; i++ )); do
                filler="${filler}${ch}"
        done

        printf "%s%s%s" "$filler" "$1" "$filler"
        [[ $(( (TERM_COLS - str_len) % 2 )) -ne 0 ]] && printf "%s" "$ch"
        printf "\n"

        return 0
}

And, finally, I have the line itself that's causing the issue. I've only tried this with this one line, so here it is:

#!/usr/bin/env bash

source ~/.scripts/bagcolors
source ~/.scripts/printcen

printcen " ${fcolors[cyan]} ----========================---- ${fcolors[default]}" " "

When I run the file, the printcen function seems to just print the colour codes exactly, including the escape sequence, as if it were just a regular string. So far, I've tried putting spaces between the colour codes and the text itself, but that didn't seem to change anything.

Any help with this would be appreciated.

2

There are 2 answers

1
Diego Torres Milano On BEST ANSWER

The problem with your approach is that you'll be counting invisible chars as part of the string.

Do this (after you fix the escape sequences as indicated by @gniourf_gniourf)

l=$(printcen '----========================----')
printf '%s%s%s\n' "${fcolors[cyan]}" "$l" "${fcolors[default]}"
0
David C. Rankin On

The second issue is you are putting the ANSI escapes through the "%s" conversion specifier to printf in printcen rather than making them part of the format string. You have two options:

printf "%s$1%s" "$filler" "$filler"

(the above is the preferred way)

Or, just use command substitution to expand the escapes:

printf "%s%s%s" "$filler" $(printf "$1") "$filler"

(costs an additional subshell)

Either way should work. Though it is worth the normal caution about using ANSI escapes to begin with, there isn't really a guarantee they work outside the VT terminal emulation. So your script using them won't be portable. (but they are nice for decorative use)