Perché printf "sta restringendo" umlaut?


54

Se eseguo il seguente semplice script:

#!/bin/bash
printf "%-20s %s\n" "Früchte und Gemüse"   "foo"
printf "%-20s %s\n" "Milchprodukte"        "bar"
printf "%-20s %s\n" "12345678901234567890" "baz"

Stampa:

Früchte und Gemüse foo
Milchprodukte        bar
12345678901234567890 baz

vale a dire, il testo con umlaut (come ü) viene "ridotto" di un carattere per umlaut.

Certamente, ho qualche impostazione sbagliata da qualche parte, ma non sono in grado di capire quale potrebbe essere.

Ciò si verifica se la codifica del file è UTF-8.

Se cambio la sua codifica in latin-1, l'allineamento è corretto, ma le umlaut sono rese sbagliate:

Frchte und Gemse   foo
Milchprodukte        bar
12345678901234567890 baz

14
Ti aspetti che printf sia a conoscenza di UTF-8 e di altri set di caratteri multibyte?
frostschutz,

16
Sembra che stia contando i byte anziché i caratteri; vedere echo Früchte und Gemüse | wc -c -mper la differenza.
Stephen Kitt,

7
Lo è @frostschutz Zsh printf.
Stephen Kitt,

10
Sì, mi aspetto che printf sia a conoscenza (almeno) di UTF-8.
René Nyffenegger,

12
Beh, non lo è. Buona fortuna ;-)
frostschutz

Risposte:


87

POSIX richiede printf 'il %-20scontare quelli 20 in termini di byte e non personaggi , anche se questo ha poco senso, come printfè quello di stampare il testo formattato (vedi la discussione presso il Gruppo di Austin (POSIX) e bashmailing list).

L' printfintegrato bashe la maggior parte delle altre shell POSIX lo onorano.

zshignora quel requisito sciocco (anche in shemulazione), quindi printffunziona come ci si aspetterebbe lì. Lo stesso per l' printfintegrato di fish(non una shell simile a POSIX).

Il ücarattere (U + 00FC), quando codificato in UTF-8, è composto da due byte (0xc3 e 0xbc), il che spiega la discrepanza.

$ printf %s 'Früchte und Gemüse' | wc -mcL
    18      20      18

Quella stringa è composta da 18 caratteri, è larga 18 colonne ( -Lessendo wcun'estensione GNU per riportare la larghezza di visualizzazione della linea più ampia nell'input) ma è codificata su 20 byte.

In zsho fish, il testo verrebbe allineato correttamente.

Ora, ci sono anche personaggi che hanno una larghezza 0 (come combinare caratteri come U + 0308, la combinazione di diaresi) o hanno una doppia larghezza come in molti script asiatici (per non parlare dei caratteri di controllo come Tab) e addirittura zshnon si allineano quelli correttamente.

Esempio, in zsh:

$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
  u|
  ü|
 ü|
  ᄀ|

In bash:

$ printf '%3s|\n' u ü $'u\u308' $'\u1100'
  u|
 ü|
ü|
ᄀ|

ksh93ha una %Lsspecifica di formato per contare la larghezza in termini di larghezza di visualizzazione .

$ printf '%3Ls|\n' u ü $'u\u308' $'\u1100'
  u|
  ü|
  ü|
 ᄀ|

Ciò non funziona ancora se il testo contiene caratteri di controllo come TAB (come potrebbe? printfDovrebbe sapere quanto distano i punti di tabulazione nel dispositivo di output e in quale posizione inizia a stampare). Funziona per caso con i caratteri backspace (come roffnell'output in cui è scritto X(grassetto X) X\bX) sebbene ksh93consideri che tutti i caratteri di controllo abbiano una larghezza di -1.

Come altre opzioni, puoi provare:

printf '%s\t|\n' u ü $'u\u308' $'\u1100' | expand -t3

Funziona con alcune expandimplementazioni (non per GNU).

Sui sistemi GNU, puoi usare GNU i awkcui printfconteggi in caratteri (non byte, non larghezze di visualizzazione, quindi non sono ancora OK per i caratteri 0-larghezza o 2-larghezza, ma OK per il tuo esempio):

gawk 'BEGIN {for (i = 1; i < ARGC; i++) printf "%-3s|\n", ARGV[i]}
     ' u ü $'u\u308' $'\u1100'

Se l'output arriva a un terminale, è anche possibile utilizzare sequenze di escape di posizionamento del cursore. Piace:

forward21=$(tput cuf 21)
printf '%s\r%s%s\n' \
  "Früchte und Gemüse"    "$forward21" "foo" \
  "Milchprodukte"         "$forward21" "bar" \
  "12345678901234567890"  "$forward21" "baz"

2
Questo non è corretto Il ücarattere può essere composto come u+ ¨, ovvero 3 byte. Nel caso della domanda, è codificato come 2 caratteri, ma non tutti üvengono creati allo stesso modo.
Ismael Miguel,

6
@IsmaelMiguel, ha u\u308due caratteri ( wc -malmeno in Unix / senso) per un glifo / graphem / graphem-cluster ed è già menzionato e incluso in questa risposta.
Stéphane Chazelas,

"ha poco senso dato che printf è stampare il testo" Beh, si potrebbe sostenere che printf si occupa dei caratteri C (byte); non dovrebbe occuparsi delle impostazioni locali del testo e non dovrebbe avere l'onere di comprendere la codifica del set di caratteri (possibilmente multibyte). Ma questa linea di difesa è in conflitto con i requisiti (ISO C99) secondo cui il troncamento dei byte "% s" non dovrebbe tradursi in testi "non validi" (caratteri troncati). Anche in questo caso Glibc fallisce (non stampa nulla). Un vero casino. postgresql.org/message-id/…
leonbloy

@leonbloy, potrebbe dare un senso a C printf(3)(poco senso dopo quel requisito C99 che stai menzionando, grazie per quello), ma non l' printf(1)utilità come ogni operatore di shell o altra utilità di testo tratta i caratteri (o sono stati modificati per gestire anche i caratteri come wcche ha un -m(mentre è -crimasto byte ) o cutche ha un -bdopo -cpotrebbe significare qualcosa di diverso dai byte).
Stéphane Chazelas,

Anche se utilizzava caratteri anziché byte, non sarebbe comunque adatto per allineare le colonne. Devi sapere quante celle terminali occupa ogni personaggio, che varia in base al carattere (0-2).
R ..

10

Se cambio la sua codifica in latin-1, l'allineamento è corretto, ma le umlaut sono rese sbagliate:

Frchte und Gemse   foo
Milchprodukte        bar
12345678901234567890 baz

In realtà no, ma il tuo terminale non parla latino-1, e quindi ottieni spazzatura piuttosto che umlaut.

Puoi risolvere questo problema usando iconv:

printf foo bar | iconv -f ISO8859-1 -t UTF-8

(o esegui semplicemente l'intero script della shell convogliato in iconv)


3
Questo è un commento utile ma non risponde alla domanda principale.
Gerrit,

1
@gerrit come è così? Se printf fa la cosa giusta quando si stampa in latino1, quindi stamparlo in latino1 e convertirlo in UTF-8 in seguito? Mi sembra una soluzione corretta per la domanda principale.
Wouter Verhelst

1
La domanda principale è "Perché si sta restringendo umlaut", la risposta (come in altre risposte) è "perché non supporta utf-8". Non sta chiedendo perché le umlaut siano rese sbagliate o come posso correggere il rendering delle umlaut . In entrambi i casi, il tuo suggerimento è utile per il sottoinsieme di utf-8 che può essere rappresentato come iso8859-1 (solo).
Gerrit,

4
@WouterVerhelst, sì, tuttavia, può applicarsi solo al testo che può essere codificato in un set di caratteri a byte singolo.
Stéphane Chazelas,

3
Anch'io ho letto la domanda come "come posso ottenere l'output giusto" piuttosto che "Non mi interessa l'output difettoso, purché sappia perché".
Mr Lister,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.