日本語と printf の相性
Perl に限らないが、プログラムの中で printf 系の関数を使って日本語を出力しようとすると、大概の場合思ったような結果を得られない。今時は GUI や HTML で表示したりすることが多いので、それほど問題にならないのかもしれないが、やはり必要になることはある。printf を使わずに済ませることも工夫次第で可能だが、やはりあれば便利だ。
Text::VisualPrintf
Text::VisualPrintf
は、マルチバイト文字を処理するための Perl モジュールだ。最初に作ったのは2011年で MBprintf
という名前だったが、2017年に Text::VisualWidth::PP
を使うように修正した際に、今の名前に変更した。なぜそうしたかというと、やってる処理がほとんど同じで、より優れた部分があったからだ。ただ、足りない部分もあったので、それを提案して採り入れてもらえたので乗り換えた。
use Text::VisualPrintf qw(vprintf vsprintf);
のように読み込むと vprintf
と vsprintf
という2つの関数が使えるようになる。
使い方については、解説するまでもあるまい。実際のコードの一部を載せておこう。
vprintf("%3d: %${from_max}s => %-${to_max}s",
$i + 1, $from_re // '', $to // '');
出力例はこんな感じだ。揃っていないと大変読みにくい。

ちなみに、内部で利用している Text::VisualWidth::PP
には Unicode の結合文字に対応する修正を入れてもらってあるので、日本語に限らず大方の言語は正しく処理できるはずだ。
実装
実装はかなり手抜きだ。Perl の sprintf は、文字幅を考慮しないので、マルチバイト文字を含むパラメータは、含まない文字列に置き換えてしまう。そして、sprintf で出力した結果を変換して元の文字列を復活させる。
アルゴリズムは次の通り。
- まず、制御文字を含めてフォーマット文字列で使われていない文字2つ選ぶ。普通は
^A
と^B
が選ばれるはずだ。これを$a
,$b
とする。 - 置換の対象となる引数を
$a
で始まってそれに$b
の繰り返しが続く文字列に返還する。 -
sprintf
の結果の/$a$b*/
に対応する部分を、順番にセーブしておいた文字列に変換する。
こうすることで、変換結果が最悪1カラムになってしまったとしても間違いなく変換することが可能だ。
Text::VisualPrintf::IO
今回、このモジュールを作ってみた。やっていることは単純で、重要なのはこの1行だけだ。
*IO::Handle::printf = \&Text::VisualPrintf::printf;
IO::Handle の printf
メソッドを置き換えているので、次のように使えるようになる。
use Test::More;
use Text::VisualPrintf::IO qw(printf);
open OUT, ">", \(my $out) or die;
OUT->printf("%10s\n", "あいうえお");
close OUT;
is( decode('utf8', $out), "あいうえお\n", 'FH->printf' );
IO::Handle
の実装を変えると IO::File
等でも使える。残念ながら
printf OUT "%10s\n", "あいうえお";
のような使い方はできない。vprintf
というメソッドも設定してあるので、次のように使うことはできる。
vprintf OUT "%10s\n", "あいうえお";
いい考えがあればおしえてほしい。
ただ、どちらかと言うと vsprintf
を使って普通に出力する方がお勧めだ。
インストール
Text::VisualPrintf
Perl プログラマなら cpanm はインストールしてあるだろうから、これで。
$ cpanm Text::VisualPrintf
いないと思うけど、ない人はこれでどうぞ。
$ curl -sL http://cpanmin.us | perl - Text::VisualPrintf
SEE ALSO
2020.02.06 追記
Truncation
この記事を書いた時には、与えた文字列が短くなるような操作には対応していなかったのだが、最新バージョンでは対応するようになっている。ただし、最低でも2バイトは残ってくれないと駄目だ。
短縮するために Text::ANSI::Fold
モジュールを使用したので、perl v5.14 以上の対応となった。
Text::VisualPrintf::IO
Text::VisualPrintf::IO
は、import パラメータを受け取るように変更した。printf
と vprintf
を指定することができる。
2020.10 追記
その後のアップデートで、1バイトの短縮に対応し、2020年9月にリリースした 3.06 からは、0バイトへの短縮、つまり対応する要素が消滅してしまう場合にも対応している。