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"