Search code examples
bashunicodeprintfstring-length

Printing and padding strings with bash/printf


In bash scripts I like to use printf "%-20s" "some string" to create columns that line up. That works great with regular text, but not really for multi-byte unicode, nor if using any kind of terminal decoration.

Works great:

for i in string longer_string Some_kind_of_monstrosity ; do
   printf "%-20s" $i ; echo " OK"
done

Everything is reasonably well lined up:

string               OK
longer_string        OK
Some_kind_of_monstrosity OK

However - it doesn't work very well with multi-byte unicode or colour codes:

printred () { tput setaf 1; printf %b "$*"; tput sgr0; }
printf "%-20s" test             ; echo " NOK"
printf "%-20s" $(printred RED)  ; echo " NOK"
printf "%-20s" "★★★★"         ; echo " NOK"

It looks like both the bash builtin printf and the coreutils/printf simply count the number of bytes in the string, rather than how many character that will be visible on the output:

test                 NOK
RED       NOK
★★★★         NOK

Is there a way to achieve this nicely in bash? (I'm using bash 5.0.17, but I'm not averse to using some other tool.)


Solution

  • I have found a potential answer:

    printred () { tput setaf 1; printf %b "$*"; tput sgr0; }
    /bin/printf "%s" test                          ; tput hpa 20; echo " OK"
    /bin/printf "%s" $(printred RED)               ; tput hpa 20; echo " OK"
    /bin/printf "%s" "some kind of over-long line" ; tput hpa 20; echo " OK"
    /bin/printf "%s" "★★★★"                      ; tput hpa 20; echo " OK"
    

    This results in everything neatly lined up!

    screenshot

    Even though over-long lines get truncated rather than pushing everything along. Which could be a bug or a feature depending on use-case. (By using a tput el, to erase to the end-of-line it looks marginally neater.)