POSIX requer printf
é %-20s
para contar os 20 em termos de bytes não caracteres apesar de que faz pouco sentido como printf
é imprimir texto , formatado (ver discussão no Grupo de Austin (POSIX) e bash
listas de discussão).
O printf
embutido bash
e a maioria dos outros cartuchos POSIX honram isso.
zsh
ignora esse requisito bobo (mesmo em sh
emulação) e printf
funciona como você esperaria lá. O mesmo para o printf
built-in fish
(não é um shell semelhante ao 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 cadeia é composta por 18 caracteres, tem 18 colunas de largura ( -L
sendo uma wc
extensão GNU para relatar a largura de exibição da linha mais larga na entrada), mas é codificada em 20 bytes.
Em zsh
ou fish
, o texto seria alinhado corretamente.
Agora, também existem caracteres com largura 0 (como combinar caracteres como U + 0308, a diarese combinada) ou largura dupla como em muitos scripts asiáticos (para não mencionar caracteres de controle como Tab) e nem zsh
alinharem aqueles corretamente.
Exemplo, em zsh
:
$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
Em bash
:
$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
ksh93
possui uma %Ls
especificação de formato para contar a largura em termos de largura de exibição .
$ printf '%3Ls|\n' u ü $'u\u308' $'\u1100'
u|
ü|
ü|
ᄀ|
Isso 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 em que posição ele começa a imprimir). Ele funciona acidentalmente com caracteres de backspace (como na roff
saída em que X
(negrito X
) é escrito como X\bX
), embora ksh93
considere 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 expand
implementações (embora não seja do GNU).
Nos sistemas GNU, você pode usar o GNU awk
cujas printf
contagens em caracteres (não bytes, nem larguras de exibição, ainda não estão OK para os caracteres de 0 ou 2 de largura, mas sim 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 pode usar seqüências de escape de posicionamento do cursor. Gostar:
forward21=$(tput cuf 21)
printf '%s\r%s%s\n' \
"Früchte und Gemüse" "$forward21" "foo" \
"Milchprodukte" "$forward21" "bar" \
"12345678901234567890" "$forward21" "baz"