5

一部のデータにPerl または C を使用する場合printf、次のように各列の幅を制御するためにそれらの形式を試しました。

printf("%-30s", str);

しかし、str に漢字が含まれていると、列が期待どおりに整列しません。添付画像をご覧ください。

私の知る限り、ubuntuの文字セットエンコーディングはzh_CN.utf8です。utf-8エンコーディングには1〜4バイトの長さがあります。漢字は 3 バイトです。私のテストでは、printf のフォーマット コントロールが漢字を 3 としてカウントすることがわかりましたが、実際には 2 つの ascii 幅として表示されます。

したがって、実際の表示幅は予想どおり一定ではなく、漢字の数に関連する変数です。

Sw(x) = 1 * (w - 3x) + 2 * x = w - x

w は予想される幅の制限、x は漢字の数、Sw(x) は実際の表示幅です。

そのため、漢字 str が多く含まれるほど短く表示されます。

どうすれば欲しいものを手に入れることができますか? printf の前に漢字を数えますか?

私の知る限り、すべての中国語またはすべてのワイド文字でさえ、2 幅で表示されるのに、なぜ printf は 3 とカウントするのでしょうか? UTF-8 のエンコーディングは表示長とは関係ありません。

4

1 に答える 1

7

printfはい、これは私が認識しているすべてのバージョンの問題です。この回答とこの回答でも、この問題について簡単に説明します。

Cの場合、これを行うライブラリを知りませんが、誰かがそれを持っているとしたら、それはICUでしょう.

Perl の場合、Unicode::GCStringモジュール フォーム CPAN を使用して、Unicode 文字列が占める印刷列の数を計算する必要があります。これは、 Unicode Standard Annex #11: East Asian Widthを考慮に入れています。

たとえば、1 列を占めるコード ポイントもあれば、2 列を占めるコード ポイントもあります。文字の組み合わせや非表示の制御文字など、列をまったく占有しないものもあります。このクラスにはcolumns、文字列が占める列の数を返すメソッドがあります。

これを使用して Unicode テキストを垂直方向に配置する例をここに示します。文字と「ワイド」アジア表意文字 (CJK 文字) を組み合わせたものを含む、一連の Unicode 文字列を並べ替え、垂直方向に並べることができます。

サンプル端末出力

umenu適切に配置された出力を出力する小さなデモ プログラムのコードを以下に示します。

また、はるかに野心的なUnicode::LineBreakモジュールにも興味があるかもしれません。前述のUnicode::GCStringクラスは、その小さなコンポーネントにすぎません。このモジュールはよりクールで、 Unicode Standard Annex #14: Unicode Line Breaking Algorithmを考慮しています。

umenu以下は、Perl v5.14 でテストされた小さなデモのコードです。

 #!/usr/bin/env perl
 # umenu - demo sorting and printing of Unicode food
 #
 # (obligatory and increasingly long preamble)
 #
 use utf8;
 use v5.14;                       # for locale sorting
 use strict;
 use warnings;
 use warnings  qw(FATAL utf8);    # fatalize encoding faults
 use open      qw(:std :utf8);    # undeclared streams in UTF-8
 use charnames qw(:full :short);  # unneeded in v5.16

 # std modules
 use Unicode::Normalize;          # std perl distro as of v5.8
 use List::Util qw(max);          # std perl distro as of v5.10
 use Unicode::Collate::Locale;    # std perl distro as of v5.14

 # cpan modules
 use Unicode::GCString;           # from CPAN

 # forward defs
 sub pad($$$);
 sub colwidth(_);
 sub entitle(_);

 my %price = (
     "γύρος"             => 6.50, # gyros, Greek
     "pears"             => 2.00, # like um, pears
     "linguiça"          => 7.00, # spicy sausage, Portuguese
     "xoriço"            => 3.00, # chorizo sausage, Catalan
     "hamburger"         => 6.00, # burgermeister meisterburger
     "éclair"            => 1.60, # dessert, French
     "smørbrød"          => 5.75, # sandwiches, Norwegian
     "spätzle"           => 5.50, # Bayerisch noodles, little sparrows
     "包子"              => 7.50, # bao1 zi5, steamed pork buns, Mandarin
     "jamón serrano"     => 4.45, # country ham, Spanish
     "pêches"            => 2.25, # peaches, French
     "シュークリーム"    => 1.85, # cream-filled pastry like éclair, Japanese
     "막걸리"            => 4.00, # makgeolli, Korean rice wine
     "寿司"              => 9.99, # sushi, Japanese
     "おもち"            => 2.65, # omochi, rice cakes, Japanese
     "crème brûlée"      => 2.00, # tasty broiled cream, French
     "fideuà"            => 4.20, # more noodles, Valencian (Catalan=fideuada)
     "pâté"              => 4.15, # gooseliver paste, French
     "お好み焼き"        => 8.00, # okonomiyaki, Japanese
 );

 my $width = 5 + max map { colwidth } keys %price;

 # So the Asian stuff comes out in an order that someone
 # who reads those scripts won't freak out over; the
 # CJK stuff will be in JIS X 0208 order that way.
 my $coll  = new Unicode::Collate::Locale locale => "ja";

 for my $item ($coll->sort(keys %price)) {
     print pad(entitle($item), $width, ".");
     printf " €%.2f\n", $price{$item};
 }

 sub pad($$$) {
     my($str, $width, $padchar) = @_;
     return $str . ($padchar x ($width - colwidth($str)));
 }

 sub colwidth(_) {
     my($str) = @_;
     return Unicode::GCString->new($str)->columns;
 }

 sub entitle(_) {
     my($str) = @_;
     $str =~ s{ (?=\pL)(\S)     (\S*) }
              { ucfirst($1) . lc($2)  }xge;
     return $str;
 }

ご覧のとおり、その特定のプログラムで機能させるための鍵は、上記で定義した他の関数を呼び出すだけのこのコード行であり、私が説明したモジュールを使用します。

print pad(entitle($item), $width, ".");

これにより、ドットを塗りつぶし文字として使用して、指定された幅にアイテムがパディングされます。

はい、それほど便利ではありませんprintfが、少なくとも可能です。

于 2012-05-26T07:37:53.450 に答える