POSIX requer printf %-20s para contar aqueles 20 em termos de bytes não caracteres mesmo que isso faça pouco sentido pois printf é imprimir texto , formatado ( veja a discussão no Austin Group (POSIX) e bash listas de discussão).
O printf incorporado em bash e a maioria das outras caixas POSIX honram isso.
zsh ignora esse requisito bobo (mesmo em sh emulação), então printf funciona como esperado. O mesmo para o printf incorporado de fish (não um shell parecido com POSIX).
O caractere ü (U + 00FC), quando codificado em UTF-8, é composto de dois bytes (0xc3 e 0xbc), o que explica a discrepância.
$ printf %s 'Früchte und Gemüse' | wc -mcL
18 20 18
Essa string é feita de 18 caracteres, tem 18 colunas de largura ( -L sendo uma extensão GNU wc para relatar a largura de exibição da linha mais ampla na entrada), mas é codificada em 20 bytes.
Em zsh ou fish , o texto seria alinhado corretamente.
Agora, também há caracteres com 0-width (como combinar caracteres como U + 0308, a combinação de diaresis) ou ter largura dupla como em muitos scripts asiáticos (sem mencionar os caracteres de controle como Tab) e até zsh não os alinharia corretamente.
Exemplo, em zsh :
$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
Em bash :
$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
ksh93 tem uma especificação de formato %Ls para contar a largura em termos de largura exibição .
$ printf '%3Ls|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
Que ainda não funciona se o texto contiver caracteres de controle como TAB (como poderia? printf teria que saber a que distância as paradas de tabulação estão no dispositivo de saída e qual posição começa a imprimir em). Ele funciona acidentalmente com caracteres de retrocesso (como na roff output em que X (negrito X ) é gravado como X\bX ), já que ksh93 considera todos os caracteres de controle como tendo uma largura de -1 .
Como outras opções, você pode tentar:
printf '%s\t|\n' u ü $'u\u308' $'\u1100' | expand -t3
Isso funciona com algumas implementações de expand (não do GNU).
Em sistemas GNU, você pode usar o GNU awk cujo printf conta em caracteres (não bytes, não exibir larguras, portanto ainda não está OK para os caracteres de 0 ou 2 de largura, mas OK para sua amostra ):
gawk 'BEGIN {for (i = 1; i < ARGC; i++) printf "%-3s|\n", ARGV[i]}
' u ü $'u\u308' $'\u1100'
Se a saída for para um terminal, você também poderá usar sequências de escape de posicionamento do cursor. Como:
forward21=$(tput cuf 21)
printf '%s\r%s%s\n' \
"Früchte und Gemüse" "$forward21" "foo" \
"Milchprodukte" "$forward21" "bar" \
"12345678901234567890" "$forward21" "baz"