2024/02/13 22:13:39

Perl: 文字コードとutf8フラグについて

index

文字コードとutf8フラグ

Perl において、「文字コード」と「utf8フラグ」は別物です。文字コードの話の中で utf8フラグが語られるので、同じもののように思っている人も多いかもしれませんが、別物です。いや、たとえば同じものだとしても、別物だと思った方が理解しやすいと思います。
文字コード は文字とコードのマッピングに名前をつけたもので、utf8フラグは、Perlにおける文字列の扱いに付随するフラグのことです。

たとえば、以下のようなソースコードをレガシーな感じに EUC-JP で書いていたとして、

my $euc_str = 'ソースが EUC-JP なのでここは EUC-JP';

$euc_str には EUC-JP の文字列が格納されています。 これを、utf8フラッグド(utf8 flag on)にしてみます。

use Encode;

my $euc_str = 'ソースが EUC-JP なのでここは EUC-JP';

my $euc_str_flagged = Encode::decode('eucjp', $euc_str);

print "flagged!" if utf8::is_utf8($euc_str_flagged);

Encode::decode によって、utf8フラグが有効になった文字列が格納されています。 utf8::is_utf8 は引数に渡された文字列が utf8フラグON なら真値を返します。

utf8フラグ は 文字コードの UTF-8 とあまり関係ない

ピンと来た人もいるかもしれませんが、「utf8フラグ」は文字コードの「UTF-8」とあまり関係ありません。上の例のように EUC-JP でも「utf8フラグON」という状態はあり得るのです。 個人的なことを言えば、utf8フラグは「unicodeフラグ」という名前の方があってる気がします。

文字コード変換も utf8フラグの操作も Encode で

文字コードの変換、utf8フラグの操作は以下のようにしています。

文字コード変換

use Encode;

my $euc_str = 'ソースが EUC-JP なのでここは EUC-JP';

Encode::from_to($euc_str, 'eucjp', 'utf8'); # EUC-JP --> UTF-8

Encode::from_to は渡した文字列そのものが変換されてしまうので、返り値で欲しい場合にはちょっと使い勝手が良くないかもしれません。そんなときは、自前でルーチンを組んでしまえばよいと思います(長い文字列を何度も受け渡しすると遅くなりそうですが、、、)。

sub _from_to {
    my $str  = shift;
    my $from = shift;
    my $to   = shift;

    Encode::from_to($str, $from, $to);

    return $str;
}

また、下のように、utf8フラグON の文字列は、いかような文字コードにも任意に出力ができます。
(ターミナルの文字コードを変えて何度か実行してみてください)

use utf8;
use Encode;

# このソースは [UTF-8]

my $utf8_str = 'フラッグドな文字列ですよ!';

my $encoded_eucjp_str = Encode::encode('eucjp', $utf8_str);
print "$encoded_eucjp_str\n"; # EUC-JP に encode して出力

my $encoded_cp932_str  = Encode::encode('cp932', $utf8_str);
print "$encoded_cp932_str\n"; # cp932(Shift_JIS) に encode して出力

my $encoded_utf8_str  = Encode::encode('utf8', $utf8_str);
print "$encoded_utf8_str\n";  # UTF-8 に encode して出力

上のコードで $utf8_str が utf8フラグON なのは「use utf8;」の効用です。

utf8フラグ の操作

Encodeモジュールの encode/decode で操作します。
(utf8::* というクラスでも utf8フラグを操作できますが、個人的にはあまり利用していません。)

use Encode;

# このコードは [UTF-8]

my $str = '雀の往来';

my $decoded = Encode::decode('utf8', $str);     # utf8フラグ ON
my $encoded = Encode::encode('utf8', $decoded); # utf8フラグ OFF

encode/decode というメソッドは、いろいろなモジュールで利用されているので、なるべく Encode::encode という風にクラス名付で呼んでおくと幸せに近づけます。
エンコードとデコードのどっちがどっちだかわからなくなりがちですが、個人的には「エン」/「デ」、という風に「濁点が付いている方がフラグも付ける」と記憶しています。

Encode::encode_utf8/decode_utf8

扱う文字列が UTF-8 なら、encode_utf8/decode_utf8 を利用した方が良い。

use Encode;

my $str = 'マルチバイト文字列'; # no flag

# my $decoded = Encode::decode('utf8', $str);
my $decoded = Encode::decode_utf8($str); # utf8フラグ ON


# my $encoded = Encode::encode('utf8', $decoded);
my $encoded = Encode::encode_utf8($decoded); # utf8フラグ OFF

utf8 プラグマ

UTF-8 で書かれたソースコードで、「use utf8;」すると、リテラルが utf8フラグON になります。

#!/usr/bin/perl
use strict;
use warnings;

use utf8;

my $str = '日本語の試験';

print "$str\n";

print length($str). "\n"; # 6

これを実行すると下のような結果になります。

$ ./test.pl
Wide character in print at ./test.pl line 9.
日本語の試験
6

ポイントは2点あります。

  • Wide character in print
  • length で“文字数”が返る

Wide character in print

use utf8; の影響というわけではありませんが、utf8フラグON の文字列をそのまま出力すると、「Wide character in print」と怒られます。
以下のように、utf8フラグ を落として(*)出力することで回避できます。

#!/usr/bin/perl
use strict;
use warnings;

use utf8;

use Encode;

my $str = '日本語の試験';

print Encode::encode($str) . "\n"; # *

print length($str). "\n";

length で“文字数”が返る

use utf8; すると、文字列まわりの関数やリテラルが、バイト列ではなく、きちんと文字単位で扱われるようになります。正規表現などもマルチバイト文字を正しく文字単位で扱ってくれるので、Shift_JIS や EUC-JP でやっていたような BK は必要なくなって便利です。

#!/usr/bin/perl
use strict;
use warnings;

use utf8;

my $str = '日本語の試験'; # utf8 flag ON

print length($str) . "\n"; # 6(文字)

{
    no utf8;

    print length('日本語の試験') . "\n"; # 18(byte)

    # no utf8 なスコープで定義しなおすと、、、
    $str = '日本語の試験';
}

print length($str) . "\n"; # 18(byte)

use utf8; は no utf8; でスコープ内の影響を解除できます。
ただし、use utf8; されたスコープの中で変数に代入したものを、no utf8; なスコープで評価しても、utf8フラグは ON になり、上の例のように、no utf8; 環境で定義したマルチバイト文字列を use utf8; なスコープで評価しても、utf8フラグはついていません。

utf8フラグ ON と OFF の文字列連結

utf8フラグが ON の文字列と OFF の文字列を連結すると、化けます。この utf8連結文字化け現象 は、慣れると化けた文字列を見て、だいたい理由がわかるようになります(え

#!/usr/bin/perl
use strict;
use warnings;

use utf8;

use Encode;

my $str_utf8    = 'フラッグドな文字列と';
my $str_no_utf8 = Encode::encode_utf8('フラグなし文字列の連結');

print $str_utf8    . "\n"; # Wide~ と言われる(一応出力される)
print $str_no_utf8 . "\n"; # OK
print $str_utf8 . $str_no_utf8 . "\n"; # 化ける

utf8フラグ による文字化けをどう回避するか

鉄則があります。

入り口で decode して、内部ではすべて flagged utf8 で扱い、出口で encode する。
これがすべてです!とにかくこの基本方針をまもっていれば幸せになれます。

ただし、多くのWAFを使っている場合は、あまりこの点を気にする必要はないかもしれません。config は外部ファイルに書けば良いし、出力はテンプレートにお任せするので。前述した鉄則はお行儀良くしている限り守られます。

きっと、問題になるのはどこかのモジュールとのやりとりだと思います(モデル部分)。モジュールのメソッドへ引き渡す値はutf8フラグがONである必要があるのか、また返り値はフラグがONなのかOFFなのか、これははっきり意識しておかなければなりません。おおくは、デコーデッドを渡して、デコーデッドのまま返ってきます。
モジュールのバージョンが変わって、このあたりの仕様が覆ると、軒並み化けてくれたりします。また、モジュールの中に文字列、特にマルチバイト文字列をハードコーディングするような場合も、utf8プラグマをそのためだけに付けなければいけなくなるので、設定ファイルに切り出すなど当然の対処が必要になります。

SEE ALSO

    $ perldoc perlunicode
    $ perldoc perlunitut
    $ perldoc perlunifaq
サイト内検索