Perlを使い、思い通りの文字コードと改行コードでファイルを作成する方法

Windows上で、EUC-JPでLFなファイルを作る - pikio公式ブログの書き直しですが(・ω・)

テキストファイルを作成する際には、文字コードに注意しなけばならない場合がある

 業務で使うシステム間のデータの受け渡しには、テキストファイルが使われることが少なくないと思います。
 「あっちのシステムから出力されたデータを、こっちのシステムに入力する。その時にはCSVファイル形式で保存して、ファイル名は何にして…」そんなマニュアルがどこの会社にも一つはあるのではないでしょうか? 私も今の会社に入るまでは「CSVファイル」なんて知りませんでしたが、業務にコンピュータを使う仕事をしていると結構身近になってしまうのがこの「CSVファイル」と「タブ区切り」です。「CSVファイル」とはデータとデータの間をカンマで区切ったテキストファイルのことです。「タブ区切り」は、カンマの代わりにタブ文字を使ったテキストファイルです。
 これらのテキストファイルは、たとえばエクセルでは「ファイル名を指定して保存」するときに、保存形式として「CSV」や「タブ区切り」を選ぶことで、作成することができます。普段こういった方法でテキストファイルを作成している場合にはまったく意識していないと思いますが、実はシステム間でこのテキストファイルをやりとりする際に気をつけていないと思わぬ不具合を引き起こしてしまう重要なポイントが文字コードなのです。
 テキストファイルの中身のデータは、ただの文字列にすぎません。文字列をデータ上であらわすための規格である文字コードは、日本語の場合、主にShift_JISEUC-JP、UTF-8UTF-16などが利用されていますが、複数の文字コードが場面場面で使いわけされていてとても紛らわしく、うっかり文字コードを間違えてしまえば、文字化けを起こしてしまうことになります。
 Windowsの環境では、ほとんどの場合、テキストファイルを保存する際の文字コードにはShift_JISが使われます。エクセルで保存したファイルも何もしなければ、Shift_JISで保存されるはずです。当然ですが、そのファイルはエクセルで開きなおすことができますし、市販されているようなWindows環境で利用される多くのシステムはShift_JISが前提でしょうから、文字コードで悩む場面というのは多くありません。別の文字コードで保存されたファイルであっても自動で文字コードを識別して対応するシステムもあります。
 ところがこれが自社開発のシステムであったり、取引会社の特有のシステムである場合、システムの文字コードの対応が限られる場合があるのです。先ほどいったとおり文字コードの問題は複雑で、面倒くさいため、システムが利用するテキストファイルの文字コードは最初から決めてしまって、それ以外の文字コードの対応は考えない方が楽なのです。

WEBサーバのOSで利用される文字コードは、EUC-JPかUTF-8

 テキストファイルの文字コードに気をつけないといけない場面というのは、主にWEBサービスとのデータのやりとりで多く見られます。それは、WEBサービスを提供するWEBサーバの基本文字コードが、Windowsで利用されるShift_JISではなく、EUC-JPやUTF-8である場合はほとんどであるためです。そのため、WEBサービスからダウンロードできるファイルの中身がUTF-8になっているであるとか、WEBサービスへ受け渡すファイルはEUC-JPでなければならないであるとかの指定があり、嫌でも文字コードを意識せざるをえない状況におかれます。別にそれが悪いことであるというわけではありません。文字コードも、きちんと理解していれば恐い問題ではないからです。

忘れがちなのが改行コード

 また、テキストファイルの行の最後を示す改行コードも、環境によって違いがあります。Windowsでは文字コードCRとLFの二つで改行を表し、WEBサーバなどのUNIX系OSではLFのみ、MacではCRが使われます。この違いも、時には問題になります。
 特にPerlでは改行を\nと表すことが一般的ですが、この\nは、Windowsで実行した場合とWEBサーバで実行した場合では、改行コードを表す文字コードが違ってしまいます。この性質を忘れてしまうと、テスト環境と本番環境で、同じプログラムが違うファイルを出力することになり、わかりづらいトラブルになることもありえます。


 今回は、EUC-JPの文字コードで、さらに改行コードはLFのみが使われているデータを利用するシステムに合わせて、テキストファイルを作成したい場合に、Perlを使って作成するにはどうすればいいのかを書きたいと思います。

基本、Perlの内部コードを使う

 前提として、プログラム内で使われる変数の文字列は、Perlの内部コードにします。
 Perlの内部コードは特殊なUTF-8ですが、これはプログラム内で

use utf8;

とすることで、プログラム内に直書きした文字列はすべて内部コードになります。
 外部から取り込む文字列についても、Encode::decode()を用いるなどして、取り込む段階で上手に内部コードに変換をしてやれば大丈夫です。
 内部コードの利用により、プログラム内の変数の文字コードの心配がなくなったところで、あとは出力の際にどう指定してやればいいのかという話になります。

バイナリモードとテキストモード

 プログラムでファイルを操作する際には、バイナリモードかテキストモードかのどちらかが指定されます。
 Perlでは何も指定しない限りテキストモードでファイルは開かれます。ところがこのテキストモードはプログラムの実行環境に依存するため、先にいったとおり、改行コードの問題がおこってしまいます。Windows環境で改行コードをCRLF以外にしてファイルを作成したい場合には、バイナリモードでなければいけません。
 バイナリモードへの切り替えは、binmode関数で行うことができます。

open my $th, '>', 'out.txt';
binmode $fh;

 これでバイナリモードで保存することができ、改行を表す\nは、LFとしてファイルに書き込まれるようになります。
 また、上記のままでは出力ファイルの文字コードUTF-8になってしまいますが、

open my $th, '>', 'out.txt';
binmode $fh, ':raw:encoding(euc-jp)';

という風に、binmodeに二つ目のパラメータを指定してやることで、EUC-JPの文字コードで出力することが可能になります。
これと同じようなパラメータを出力ファイルを開くときに

open my $th, '>:raw:encoding(euc-jp)', 'out.txt';

として指定することでも、目的の結果が得られます。
:rawの部分がバイナリモードを指定するという意味で、これは二つ目のパラメータが省略された場合の初期設定になりますので、

binmode $fh;

binmode $fh, ':raw';

は同じ動作です。
逆に

binmode $fh, ':crlf';

とすることで、バイナリモードからテキストモードに戻ることができます。
また、Windows用のShift_JISファイルを出力したい場合には、

open my $th, '>:raw:encoding(cp932)', 'out.txt';

としましょう。バイナリモードでCRLFの改行コードを出力したい場合には、\r\nと書いてください。


このように、文字コードと、それを扱うプログラミング言語の性質を理解していれば、文字コードもそんなに難しく考える必要はありませんよね( ´∀`)