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.)
Here is a sample library I wrote for an utf-8-compatible string alignment:
align.sh:
#!/bin/false
# UTF-8-compatible string alignment library
# Space pad align string to width
# @params
# $1: The alignment width
# $2: The string to align
# @stdout
# aligned string
align::left() {
local -i width=${1:?} # Mandatory column width
local -- str=${2:?} # Mandatory input string
local -i length=$((${#str} > width ? width : ${#str}))
local -i pad_right=$((width - length))
printf '%s%*s' "${str:0:length}" $pad_right ''
}
align::right() {
local -i width=${1:?} # Mandatory column width
local -- str=${2:?} # Mandatory input string
local -i length=$((${#str} > width ? width : ${#str}))
local -i offset=$((${#str} - length))
local -i pad_left=$((width - length))
printf '%*s%s' $pad_left '' "${str:offset:length}"
}
align::center() {
local -i width=${1:?} # Mandatory column width
local -- str=${2:?} # Mandatory input string
local -i length=$((${#str} > width ? width : ${#str}))
local -i offset=$(((${#str} - length) / 2))
local -i pad_left=$(((width - length) / 2))
local -i pad_right=$((width - length - pad_left))
printf '%*s%s%*s' $pad_left '' "${str:offset:length}" $pad_right ''
}
demo:
#!/usr/bin/env bash
# Demonstrates the align library
. ./align.sh
strings=(
'Früchte und Gemüse'
'Milchprodukte'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)
printf '%s\n' 'Left-aligned:'
for str in "${strings[@]}"; do
printf "| %s |\n" "$(align::left 20 "$str")"
done
printf '\n%s\n' 'Right-aligned:'
for str in "${strings[@]}"; do
printf "| %s |\n" "$(align::right 20 "$str")"
done
printf '\n%s\n' 'Center-aligned:'
for str in "${strings[@]}"; do
printf "| %s |\n" "$(align::center 20 "$str")"
done
Demonstration output:
Left-aligned:
| Früchte und Gemüse |
| Milchprodukte |
| ABCDEFGHIJKLMNOPQRST |
Right-aligned:
| Früchte und Gemüse |
| Milchprodukte |
| GHIJKLMNOPQRSTUVWXYZ |
Center-aligned:
| Früchte und Gemüse |
| Milchprodukte |
| DEFGHIJKLMNOPQRSTUVW |
simple command column meets your need.
column -t < input.txt
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With