Printf neden “küçülüyor”?


54

Aşağıdaki basit betiği uygularsam:

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

Yazdırır:

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

yani, umutlu metinler (örneğin ü) umutut başına bir karakter tarafından "küçültülür".

Kesinlikle, bir yerlerde bazı yanlış ayarlarım var, ama hangisinin olabileceğini çözemiyorum.

Bu, dosyanın kodlaması UTF-8 ise gerçekleşir.

Kodlamasını latin-1 olarak değiştirirsem, hizalama doğrudur, ancak gölgeler yanlış işlenir:

Frchte und Gemse   foo
Milchprodukte        bar
12345678901234567890 baz

14
Printf'in UTF-8 ve diğer çok baytlık karakter kümelerinin farkında olmasını mı bekliyorsunuz?
frostschutz

16
Karakter yerine bayt sayıyor gibi görünüyor; echo Früchte und Gemüse | wc -c -mfark için bkz .
Stephen Kitt

7
@frostschutz Zsh's printf.
Stephen Kitt

10
Evet, printf'in (en azından) UTF-8'in farkında olmasını bekliyorum.
René Nyffenegger

12
Eh, değil. Zor şans. ;-)
frostschutz

Yanıtlar:


87

POSIX gerektirir printf 's %-20saçısından bu 20 saymak bayt değil karakterler olarak o küçük mantıklı olsa da printfyazdırmaktır metni (tartışma biçimlendirilmiş, Austin Grubu'nda (POSIX) ve bashe-posta listeleri).

printfArasında yerleşik bashve diğer birçok POSIX kabukları o onur.

zshaptal gereksinimi ( shöykünme durumunda bile ) görmezden gelir, printforada beklediğiniz gibi çalışır. İçin aynı printfbir yerleşiği fish(bir POSIX gibi kabuk).

üUTF-8 kodlanmış karakteri (U 00FC), hatayı açıklar iki bayt (0xc3 ve 0xbc) yapılmıştır.

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

Bu dize 18 karakterden oluşur, 18 sütun genişliğindedir ( girişteki en geniş satırın görüntü genişliğini bildiren -Lbir GNU wcuzantısıdır) ancak 20 baytta kodlanır.

In zshOr fish, metni doğru hizalanmış olacaktır.

Şimdi, 0 genişlikli (U + 0308 gibi karakterleri bir araya getirme karakterleri gibi) ya da birçok Asya alfabesinde olduğu gibi (Tab gibi kontrol karakterlerinden bahsetmeden) çift genişliğe sahip ve hatta zshhizalamayan karakterler de var. bunlar düzgün.

Örnek zsh:

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

İçinde bash:

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

ksh93ekran genişliği %Lscinsinden genişliği saymak için bir format belirtimine sahiptir .

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

Metin, TAB gibi kontrol karakterleri içeriyorsa, bu hala işe yaramaz ( printfsekme duraklarının çıkış cihazında ne kadar uzakta olduklarını ve hangi konumda basmaya başladığını bilmek zorunda kalırdı). O (olduğu gibi geri silme karakterleri ile kazara çalışır roffçıkışı X(kalın X) olarak yazılır X\bX) olarak gerçi ksh93bir genişliğe sahip olarak tüm kontrol karakterleri dikkate alır -1.

Diğer seçenekler olarak deneyebilirsiniz:

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

Bu, bazı expanduygulamalarla çalışır (GNU’nun olmasa da).

GNU sistemlerinde, GNU'yu kullanabilirsiniz awkolan printf(hala değil bayt değil gösterilecek-genişlikleri, bu nedenle numune için 0 genişlikli veya 2 genişlikli karakterler için Tamam, ama tamam değil) sayılarının yanmış maddedeki:

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

Çıktı bir terminale giderse, imleç konumlandırma kaçış dizilerini de kullanabilirsiniz. Sevmek:

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

2
Bu yanlış. üCaracter olarak oluşabilir u+ ¨3 bayt. Sorunun söz konusu olması halinde 2 karakter olarak kodlanmıştır, fakat hepsi üeşit olarak yaratılmamıştır.
Ismael Miguel

6
@IsmaelMiguel, bir glif / graphem / graphem-cluster için u\u308iki karakterdir ( wc -men azından Unix / anlamda) ve bu cevabın içinde zaten belirtilmiştir.
Stéphane Chazelas

"printf'in metin basması kadar az anlamlı olduğu" Peki, printf'in C karakterleriyle (baytlar) ilgilendiği söylenebilir; metin yerelleriyle ilgilenmemeli ve (muhtemelen çok baytlı) karakter kümesi kodlamasını anlama yükü olmamalıdır. Ancak bu savunma hattı, "% s" baytının kesilmesinin "geçersiz" metinlerle (kesikli karakterler) sonuçlanmaması gerektiği (ISO C99) gereklilikleriyle çelişir. Glibc bu durumda bile başarısız olur (hiçbir şey basmaz). Gerçek bir karmaşa. postgresql.org/message-id/…
leonbloy

@leonbloy, o C'nin mantıklı olabilir printf(3)(bunun için, teşekkürler söz olduğunuzu C99 şartı sonra küçük duygusu), ancak printf(1)karakterler ile her kabuk operatörü veya diğer metin yarar anlaşması olarak yarar (veya modifiye edilmiş ayrıca karakterleri ile uğraşmak gibi wcbir var olan -m(ederken -ckaldı bayt ) ya da cutbir got -bsonra -c) bayt'tan başka bir şey anlamına gelebilir.
Stéphane Chazelas

Bayt yerine karakter kullansa bile sütunların hizalanması için uygun olmazdı. Her karakterin işgal ettiği kaç tane terminal hücresi olduğunu bilmeniz gerekir; bu, karaktere göre değişir (0-2).
R. ..

10

Kodlamasını latin-1 olarak değiştirirsem, hizalama doğrudur, ancak gölgeler yanlış işlenir:

Frchte und Gemse   foo
Milchprodukte        bar
12345678901234567890 baz

Aslında, hayır, ama terminaliniz latin-1'i konuşmuyor ve bu nedenle umlautlardan ziyade önemsiz oluyorsunuz.

İconv kullanarak bunu düzeltebilirsiniz:

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

(veya sadece iconv içine yerleştirilmiş bütün kabuk betiğini çalıştırın)


3
Bu faydalı bir yorumdur ancak ana soruya cevap vermemektedir.
gerrit

1
@gerrit nasıl yani? Latince1'de yazdırırken printf doğru olanı yaparsa, latin1'de yazdırmasını ve daha sonra UTF-8'e dönüştürmesini sağlayın mı? Bana temel soru için uygun bir düzeltme gibi görünüyor.
Wouter Verhelst

1
Asıl soru “Neden küçülüyor”, yanıtı (diğer cevaplarda olduğu gibi) “utf-8'i desteklemediği için”. Umlaların neden yanlış oluşturulduğunu ya da umlaut görüntülemesini nasıl düzeltebilirim diye sormuyor . Her iki durumda da öneriniz, ut88-8 alt kümesi için iso8859-1 (yalnızca) olarak gösterilebilecek yararlıdır.
gerrit

4
@WouterVerhelst, evet, ancak yalnızca bir baytlık karakter kümesinde kodlanabilen metinler için geçerli olabilir.
Stéphane Chazelas

3
Ben de soruyu, "Neden çıktığını bildiğim sürece hatalı çıktı umrumda değil" yerine "çıktıyı nasıl doğru alabilirim" olarak okudum.
Bay Lister
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.