![[tips][perl] Perlで文字コードをいい感じに処理する方法](https://casualdevelopers.com/wp-content/uploads/2013/11/binary-code-475664_1920-700x300.jpg)
日頃のつまらないルーチンワークは全てスクリプト化している俺です。スクリプトは動的言語であれば気楽にかけるので何でもよいのですが、うちの会社の場合だと、ローカル環境はWindows、商用環境はLinuxなので、どちらでもそれなりに動くPerlをよく使います。スクリプティングでよくやるのは「ファイルを読み込み→いい感じの処理→ファイルを出力」だと思うので、そこら辺の注意点を忘れないように書いておきます。
Perlの文字コード
Perlは内部で「内部文字列(flagged utf8)」というUTF-8によくわからないフラグがついた文字コードとして扱います。そのため、外部からの文字列のやり取りには、文字コード変換が不可欠です。文字コードを考えずに扱っていると、日本語を使った瞬間に文字化けです。
#1. まずは「use utf8;」を付けて、UTF-8で保存する。
# Perlファイル (UTF-8で保存されている) # 始めにutf8プラグマを設定する。 use utf8; # 日本語の文字数(3文字)が表示される。 my $japanese = '日本語'; print length($japanese);
utf8プラグマを設定することで、「UTF-8の文字列→内部文字列」に自動変換してくれるようになります。ファイル内でPerlの関数呼び出しが正確に行われるので、日本語書き放題です。
#2. 標準入出力は「binmode」で文字コード変換する。
# 標準入出力の文字コード設定 # Windowsなら'cp932'、Linuxならだいたい'UTF-8' my $enc_os = 'cp932'; binmode STDIN, ":encoding($enc_os)"; binmode STDOUT, ":encoding($enc_os)"; binmode STDERR, ":encoding($enc_os)"; # 標準出力しても文字化けしない。 my $japanese = '日本語'; print "$japanesen";
これで、「標準入力の文字列→内部文字列→標準出力の文字列」に自動変換してくれます。OSの文字コードを指定しておけば、標準入出力を文字化けせずに扱えます。
#3. 「Encode」モジュールで入出力をデコード・エンコードする。
# 文字コード変換モジュール use Encode qw/encode decode/; sub d($) { decode($enc_os, shift) } sub e($) { encode($enc_os, shift) } # ARGVの値を受け取り、出力しても文字化けしない。 # デコード(標準入力の文字列→内部文字列) my $user_input = d(shift); # エンコード(内部文字列→標準出力の文字列) print "e($user_input)n";
例はbinmodeを使わずに、Encodeモジュールを使った場合です。デコードにより、「外部から入力された文字列→内部文字列」に変換します。エンコードにより、「内部文字列→外部に出力する文字列」に変換します。結局、「use utf8;」でも「binmode標準入出力」でも処理できない文字コード変換のために使うとよいと思います。例えば、例のARGVはデコードしなければいけません。
文字化けしないファイル入出力
それでは、文字コード変換のやり方が分かったところで、ファイル入出力の3つの方法を書きます。俺が勝手に3つに分けてみただけなので、あしからず。
#1. Encodeモジュールで文字コード変換をした場合のファイル入出力
# 文字コードがShift_JISのファイルを扱う my $enc_io = 'Shift_JIS'; sub dio($) { decode($enc_io, shift) } sub eio($) { encode($enc_io, shift) } # ファイルパス my $path = './ファイル.txt’; # ファイル入力 open my $in, "<", e($path) or die "Can't open $path : $!"; while(my $line = <$in>){ push(@data, dio($line) ); } close $in; # 何か処理する。 # ファイル出力 open my $out, ">", e($path) or die "Can't open $path : $!"; foreach my $data (@data) { print $out eio($data); } close $out;
やや長いですが、Encodeモジュールを使って確実に文字コード変換をしながら、ファイル入出力を行う方法です。OSの文字コードとファイルの文字コードが同じということはあまりないので、ファイル用に文字コードを定義しています。
また、ファイルパスからファイルハンドルを得る際に、ファイルパスをエンコードしていることにも着目してください。ファイルパスを日本語で書いても動くってことです。
#2. binmodeで文字コード変換した場合のファイル入出力
# 文字コードがShift_JISのファイルを扱う my $enc_io = 'Shift_JIS'; # ファイルパス my $path = './ファイル.txt’; # ファイル入力 open my $in, "<", e($path) or die "Can't open $path : $!"; binmode $in, ":encoding($enc_io)"; @data = <$in>; close $in; # 何か面白いことする。 # ファイル出力 open my $out, ">", e($path) or die "Can't open $file_path : $!"; binmode $out, ":encoding($enc_io)"; print $out @data; close $out;
「binmode」は標準入出力以外にもファイルハンドルに対しても使用できます。Encodeモジュールをガッツリ使うよりもコンパクトになりました。
#3. encodingで文字コード変換した場合のファイル入出力
# 文字コードがShift_JISのファイルを扱う my $enc_io = 'Shift_JIS'; # ファイルパス my $path = './ファイル.txt’; # ファイル入力 open my $in, "<:encoding($enc_io)", e($path) or die "Can't open $path : $!"; @data = <$in>; close $in; # 何か世界を変えることをする。 # ファイル出力 open my $out, ">:encoding($enc_io)", e($path) or die "Can't open $path : $!"; print $out @data; close $out;
「encoding」を使用することで、ファイル入出力の文字列を読み込んだり書き込んだ時点で内部文字列に変換してくれます。個人的には一番スマートな気がします。
はい、これで誰でもPerlで文字化けしないいい感じの処理が書けますね。
ARGVは自分でデコードしないといけない。
最後におまけです。
ここまで文字コードを処理してきて、ふと思うのが、ARGV(コマンドの引数の配列)もbinmodeとかで自動的に内部文字列に変換できないのか?という疑問です。答えは残念ながらNOのようです。ですから、せめて、シンプルにデコードしましょう。
ARGVのシンプルなデコード方法
# @ARGVのデコード @ARGV = map { d($_) } @ARGV;
うん、いい感じ。ARGVはencodingプラグマは効果がないので、Encodeモジュールで愚直にデコードしましょう。
ちなみに、ファイルハンドルをスカラー変数に入れて使っていることに気づきましたか?Perl5.6より前のバージョンでは、ファイルハンドルをベアワード(bareword)としてしか使えませんでしたが、Perl5.6以降ではスカラー変数に代入できるようになりました。ファイルハンドルをスカラー変数として扱えば、ファイルハンドルのスコープ管理ができるので、安全なコードになります。簡単なスクリプトならベアワードでいいでしょ、という意見もありますが、私は、そんな無駄な区別をするぐらいだったら全部レキシカルなスカラー変数でいいでしょ、と思います。
初の技術投稿ということで真面目に整理して書きました。次からはこんなに整理して書くことはないでしょう(笑)
環境
OS : Windows7
Perl : Perl5.16.3