対象:
CRuby
JRuby

sprintfで日本語(UTF-8)文字列の表示幅を指定する

文字コードにUTF-8を採用している環境において、sprintfで%sで幅を指定して文字列を表示させて「あれ?」と思ったことはないだろうか。UTF-8では全角1文字のバイト数は3バイトになるので、sprintfで%8s等と指定しても全角文字があると意図した通りにならない。これは以下のコードで一応解決できる。

require "kconv"
$KCODE = "UTF8"

s = "日本語"
print(sprintf("|%-*s|\n",8 + s.length - s.tosjis.length,s))

ただし、kconvによる文字コードの変換は割と不親切で、ちょっと特殊な文字だとinvalid encodingになるので、そのちょっと特殊な文字を扱いたい場合は予め削除する等の対策が必要となる。

UTF-8環境でsprintfで幅指定をしても日本語の文字列が正しく表示されない理由はこうである。例えば、JRuby 1.4.0で"|%-8s|"と指定しても、対象の文字列に全角文字が入っていると意図した通りに空白が挿入されない。"日本語"という文字列を表示させる場合、

|日本語|

と表示されるであろう。しかし、意図したのは

|日本語  |

のように"日本語"の後ろに半角スペースが2つ挿入されて表示された状態である。つまり、8sの8は半角8個分の表示領域という意図である。しかし、UTF-8の環境では"日本語"という文字列は3*3=9バイトになってしまい"日本語".lengthの値は9を示す。

sprintfでは8sの8は表示領域ではなくバイト数と理解するのだろう。このため、"日本語"という文字列は指定されたバイト数よりも多いので空白を付加する必要なしという判断なのだろう。

これがSJIS等の全角文字が2バイトの環境、すなわち全角文字のバイト数と表示領域が一致する環境であれば問題は起こらない。上記のコードではこれを利用して、表示対象の文字列をSJISに変換して必要な表示領域を計算した後、実際の表示幅に補正を加えるのである。

ただし、Rubyも1.9.1になるとString周りが大きく変わるので状況が異なる。JRuby 1.4.0では1.8モードでも1.9モードでもsの幅指定はバイト数と理解される。しかし、Ruby 1.9.1では、例えば%8sと指定するとこの8は文字数だと理解されるようだ。このため、上記の方法は使うことができない。

僕の知る限り、Ruby 1.9.1では文字数でなくバイト数で表示幅を指定する方法がない。JRubyにせよRubyにせよsprintfで%sで幅を指定したら、それはバイト数でもなく文字数でもなく表示領域の数だと理解してくれたら良いのだが...

(2010/02/26)

新着情報
【オープンソースソフトウェア環境構築】Apple silicon Macで開発環境を構築
【Rust Tips】Actix webでJSONをPOSTする
【Rust Tips】コマンドライン引数を取得する

Copyright(C) 2004-2014 モバイル開発系(K) All rights reserved.
[Home]