Jeśli wykonam następujący prosty skrypt:
#!/bin/bash
printf "%-20s %s\n" "Früchte und Gemüse" "foo"
printf "%-20s %s\n" "Milchprodukte" "bar"
printf "%-20s %s\n" "12345678901234567890" "baz"
Drukuje:
Früchte und Gemüse foo
Milchprodukte bar
12345678901234567890 baz
to znaczy tekst z umlautami (np. ü
) jest „zmniejszany” o jeden znak na umlaut.
Oczywiście, mam gdzieś jakieś złe ustawienie, ale nie jestem w stanie ustalić, który to może być.
Dzieje się tak, jeśli kodowanie pliku to UTF-8.
Jeśli zmienię kodowanie na latin-1, wyrównanie jest prawidłowe, ale umlauty są renderowane nieprawidłowo:
Fr�chte und Gem�se foo
Milchprodukte bar
12345678901234567890 baz
echo Früchte und Gemüse | wc -c -m
różnicę.printf
is.Odpowiedzi:
POSIX wymaga
printf
,%-20s
aby policzyć te 20 w kategoriach bajtów, a nie znaków, chociaż nie ma to większego sensu, jakprintf
drukowanie tekstu , formatowanie (patrz dyskusja w Austin Group (POSIX) ibash
listy mailingowe).Uwzględniają to
printf
wbudowanebash
i większość innych powłok POSIX.zsh
ignoruje to głupie wymaganie (nawet wsh
emulacji), więcprintf
działa tak, jak można się tam spodziewać. To samo dotyczyprintf
wbudowanejfish
(nie powłoki podobnej do POSIX).ü
Znak (U + 00FC), w którym zakodowane UTF-8 składa się z dwóch bajtów (0xc3 i 0xbc), co wyjaśnia różnicę.Łańcuch ten składa się z 18 znaków, ma 18 kolumn szerokości (
-L
jestwc
rozszerzeniem GNU raportującym szerokość wyświetlania najszerszej linii na wejściu), ale jest zakodowany na 20 bajtach.W
zsh
lubfish
tekst zostałby wyrównany poprawnie.Teraz są też znaki, które mają szerokość 0 (jak łączenie znaków, takich jak U + 0308, łączenie diurezy) lub mają podwójną szerokość, jak w wielu skryptach azjatyckich (nie wspominając o znakach kontrolnych, takich jak Tab), a nawet
zsh
nie wyrównywałyby te poprawnie.Przykład w
zsh
:W
bash
:ksh93
ma%Ls
specyfikację formatu, aby policzyć szerokość pod względem szerokości wyświetlania .To nadal nie działa, jeśli tekst zawiera znaki sterujące, takie jak TAB (jak to możliwe?
printf
Musiałby wiedzieć, jak daleko od siebie są tabulatory w urządzeniu wyjściowym i w jakiej pozycji zaczyna drukować). Działa przypadkowo ze znakami backspace (jak naroff
wyjściu, gdzie zapisanoX
(pogrubienieX
) jakoX\bX
), chociażksh93
uważa, że wszystkie znaki sterujące mają szerokość-1
.Jako inne opcje możesz spróbować:
Działa to z niektórymi
expand
implementacjami (choć nie GNU).W systemach GNU możesz użyć GNU,
awk
któregoprintf
liczenie jest w znakach (nie bajtach, nie szerokościach wyświetlania, więc nadal nie jest OK dla znaków o szerokości 0 lub 2 szerokości, ale OK dla twojej próbki):Jeśli dane wyjściowe trafiają do terminala, możesz także użyć sekwencji ucieczki pozycjonowania kursora. Lubić:
źródło
ü
może być złożony jakou
+¨
, czyli 3 bajty. W przypadku pytania jest zakodowane jako 2 znaki, ale nie wszystkieü
są tworzone jednakowo.u\u308
to dwa znaki (wc -m
przynajmniej w Uniksie / sensie) dla jednego glifu / graphem / graphem-klastra i jest już wspomniany i zawarty w tej odpowiedzi.printf(3)
(nie ma sensu po tym wymaganiu C99, o którym wspominasz, dziękuję za to), ale nieprintf(1)
narzędzie, ponieważ każdy operator powłoki lub inne narzędzie tekstowe radzi sobie ze znakami (lub zostały zmodyfikowane, aby również traktować znaki np.wc
który dostał bajt-m
(podczas gdy-c
został bajt ) lubcut
który otrzymał-b
później,-c
może oznaczać coś innego niż bajty).Właściwie nie, ale twój terminal nie mówi po łacinie-1, dlatego dostajesz śmieci zamiast umlautów.
Możesz to naprawić za pomocą iconv:
(lub po prostu uruchom cały skrypt powłoki przesłany do iconv)
źródło