web-dev-qa-db-ja.com

なぜprintfはウムラウトを「縮小」するのですか?

次の簡単なスクリプトを実行すると、

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

それは印刷します:

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

つまり、ウムラウト付きのテキスト(üなど)は、ウムラウトごとに1文字ずつ「縮小」されます。

確かに、どこかに間違った設定がありますが、どれが正しい設定なのかわかりません。

これは、ファイルのエンコーディングがUTF-8の場合に発生します。

エンコーディングをlatin-1に変更すると、配置は正しくなりますが、ウムラウトが正しくレンダリングされません。

Fr�chte und Gem�se   foo
Milchprodukte        bar
12345678901234567890 baz
55

POSIXrequiresprintf 's %-20s to count the 20 of the 20 ofbytes notcharactersprintftextを出力するので意味がありませんが、フォーマットされています(ディスカッション Austin Groupで)を参照してください (POSIX)および bash メーリングリスト)。

printfbashビルトインと他のほとんどのPOSIXシェルはこれを尊重します。

zshはその愚かな要件を無視するため(shエミュレーションでも)、printfは期待どおりに機能します。 printffishビルトイン(POSIXのようなシェルではない)でも同じです。

ü文字(U + 00FC)は、UTF-8でエンコードされている場合、2バイト(0xc3および0xbc)で構成されており、矛盾を説明しています。

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

その文字列は18文字で構成され、幅は18列です(-LはGNU wc拡張であり、入力の最も広い行の表示幅を報告します)。ただし、20バイトでエンコードされます。

zshまたはfishでは、テキストは正しく配置されます。

現在、幅が0の文字(U + 0308のような結合文字など)や、多くのアジア言語のスクリプトのように2倍幅の文字(タブのような制御文字は言うまでもありません)もあり、zshも使用できませんそれらを適切に揃えます。

例、zshの場合:

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

bash内:

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

ksh93には、display幅で幅をカウントする%Lsフォーマット指定があります。

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

それでもは機能しませんテキストにTABなどの制御文字が含まれている場合(---どうすればよいですか?printfは、出力デバイスのタブストップの距離と位置を知っている必要があります。で印刷を開始します)。バックスペース文字(roffの出力(X(bold X)is X\bX)と記述されている)は偶然に機能しますが、ksh93はすべての制御文字と見なされます-1の幅を持つものとして。

他のオプションとして、あなたは試すことができます:

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

これは一部のexpand実装(GNUではありません)で機能します。

GNUシステムでは、GNU awkが文字数でカウントされるprintfを使用できます(バイトではなく、表示幅ではないため、0幅では問題があります。または全角文字ですが、サンプルには問題ありません):

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

出力が端末に送られる場合は、カーソル位置エスケープシーケンスを使用することもできます。お気に入り:

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

エンコーディングをlatin-1に変更すると、配置は正しくなりますが、ウムラウトが正しくレンダリングされません。

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

実際には、いいえ、しかしあなたの端末はラテン-1を話さないので、ウムラウトではなくジャンクを取得します。

これを修正するには、iconvを使用します。

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

(または単にiconvにパイプされたシェルスクリプト全体を実行します)

10
Wouter Verhelst