Perlでファイルの文字コードを指定して読み込み&書き込み
一昨日の金曜日、ムキー!ってなったことを、落ち着いて週末に解決するべく色々やった記録です。
まず、このGoogle Spread Sheet 開いてください。普通に開けると思います。
はい。タイトルで察していただけると思うんですが、そういうことです。
では、画面左上のタイトルの下にある [ファイル] メニューからダウンロードを選び、[カンマ区切りの値 csv] を選びます。
ダウンロードのダイアログが出るので、一度どっかに mojibake.csv
ってファイル名で保存してください。
保存したcsvファイルですが、VSCode や SublimeText で普通に開き、中身を確認することができます。Windows のメモ帳(notepad.exe)や macOS のテキストエディットでも文字化けは起こりません。
ではこれを Office365 の Excel で開いてみます。
見事に文字化けです!
Office365 は macOS でも利用できるのですが、同様に文字化けします。
憎いですね。許しがたいですね。
このような状況はよくあります。
これは「文字コード」の違いが原因です。文字コードについては色々大変なのでここでは説明しません。
説明しきる力量がないともいいます・・・
- cp932: Excel をはじめとしたMicrosoft 製品(日本語版)で利用されている文字コード
- utf-8: macOS や *unix で利用されている文字コード。Google Spread Sheetもこれ
という差異が文字化けを生んだわけです。
Excel(Office365) で utf8 のcsvファイルを文字化けさせずに読み込む
もちろん、Excel なので(信頼感)こういったファイルをちゃんと取り込む術があります。
まず、csvファイルを 読み込まず に Excel を開き、メニューから [データ] を選択、その中の [テキストファイル] を選びます。画像の左から2つ目のアイコンのところです。
ウィザードの画面が出ます。
[区切り記号付き] を選び、[元のファイル] のプルダウンメニューから [Unicode (UTF-8)] を選びます。
下のプレビューで見ると(字が細いですが)、正しく表示されていることが確認できます。
あとはなりなりに進んでいくと、無事にExcelのシートに取り込むことが可能です。
Perlでやる場合
・・・別にPerlでやらなくても良いのでは?導入が長いのでは?
わかります。うちもこのエントリをボツにしようかと思いましたもん。
でも、仮にこういったファイルが100個あったとして、一個一個心を込めてExcelのウィザードを起動するの・・・?
という心の声が聞こえてきたんで書きます。
想定環境は macOS 上かPerl入学式で作った msys2 上の環境です。mojibake.csv
と同じ場所にスクリプトを作ります。
それと、今回扱うのはcsvファイルですが内容を変更しないので、csv関係のモジュールは使いません。
文字コード指定して読み込んで、文字コード指定して書き出すのみ、です。
単にファイルを指定して読むだけ
前回、前々回でやった範囲です。復習ですね。
#!/usr/bin/env perl use strict; use warnings; my $input_file = 'mojibake.csv'; open my $FH, '<', $input_file; for my $line (<$FH>) { chomp $line; print $line . "\n"; } close $FH;
文字コードを指定して読み込んで表示する
上記のコードとの違い、わかりますでしょうか。
#!/usr/bin/env perl use strict; use warnings; use Encode; # 文字コードを扱う便利モジュール my $input_file = 'mojibake.csv'; open my $FH, '<:encoding(utf8)', $input_file; # utf8の文字コードのファイルを読み込む for my $line (<$FH>) { chomp $line; print encode('utf8', $line) . "\n"; # 文字コードを指定して出力 } close $FH;
Perlで文字コードが異なるファイルや入出力に対応するときは Encode
モジュールを使うのが鉄板です。標準モジュールなのでインストールは不要です。
use Encode;
ファイルを読み込むときの第二引数 <
に、文字コード指定の :encoding(utf8)
がついています。
open my $FH, '<:encoding(utf8)', $input_file; # utf8の文字コードのファイルを読み込む
ファイルの文字コードがわかっている場合には、このように文字コードを指定して読み込みます。
そして、処理した結果を出力する時(今回は print
でターミナルに出力)には、同じように文字コードを指定しています。
print encode('utf8', $line) . "\n";`
mac と msys2 のターミナルの文字コードは utf8 なので、encode の utf8 で指定して print
しています。
文字コードを指定してファイルに書き込む(utf8)
読み込んで書き込む、ということで、ファイルハンドルも2つ用意します。$ReadFH
と $WriteFH
です。
書き込みの際のファイルハンドルは第2引数の不等号の向きが >
とファイル名(またはファイル名が入っている変数)側を向いています。そして、文字コードの指定 :encoding(ctf8)
もついています。
open my $WriteFH, '>:encoding(utf8)', $output_file; # utf8の文字コードでファイルに書き込む
まずはutf8の文字コードで書き出してみます。
#!/usr/bin/env perl use strict; use warnings; use Encode; # 文字コードを扱う便利モジュール # 読んだファイルの内容をためておく配列 my @lines; # ファイル読み込み処理 my $input_file = 'mojibake.csv'; open my $ReadFH, '<:encoding(utf8)', $input_file; # utf8の文字コードのファイルを読み込む for my $line (<$ReadFH>) { chomp $line; push @lines, $line; } close $ReadFH; # ファイル書き込み処理 my $output_file = 'utf8.csv'; open my $WriteFH, '>:encoding(utf8)', $output_file; # utf8の文字コードでファイルに書き込む for my $line (@lines) { print $WriteFH $line . "\n"; } close $WriteFH;
文字コードを指定してファイルに書き込む(cp932)
次に、いよいよExcelが素で解釈してくれる文字コード、cp932
で書き込みます。
と言っても、上記のコードの1行、書き込む際のファイルハンドルの文字コード指定 utf8
を cp932
に変えただけです。
コードはこちら。クリックすると展開されます
#!/usr/bin/env perl use strict; use warnings; use Encode; # 文字コードを扱う便利モジュール # 読んだファイルの内容をためておく配列 my @lines; # ファイル読み込み処理 my $input_file = 'mojibake.csv'; open my $ReadFH, '<:encoding(utf8)', $input_file; # utf8の文字コードのファイルを読み込む for my $line (<$ReadFH>) { chomp $line; push @lines, $line; } close $ReadFH; # ファイル書き込み処理 my $output_file = 'cp932.csv'; open my $WriteFH, '>:encoding(cp932)', $output_file; # cp932の文字コードでファイルに書き込む for my $line (@lines) { print $WriteFH $line . "\n"; } close $WriteFH;
これでやっと、Excelでそのまま開いて文字化けしないファイルができました。
Excelですんなりファイルを読み込み、文字化けもせず。素晴らしい!
逆(cp932 -> utf8)もいけます
もちろん、Excel をはじめ Windows アプリが cp932 で出力した csv ファイルを読み取り、utf8 で書き出すことも可能です。
官公庁のcsvファイルなんかはだいたい Windows や Excel で作られているみたいなので、こちらの方が数は多いでしょうね。
例えば厚生労働省の人口動態データです。右上の [ダウンロード] ボタンからダウンロードしてみてください。
これを文字コードを指定せず、Perlで読み込んで出力すると文字化けします。
では、文字コードを指定して表示してみます。
#!/usr/bin/env perl use strict; use warnings; use Encode; my $input_file = 'mi010000.csv'; open my $FH, '<:encoding(cp932)', $input_file; for my $line (<$FH>){ chomp $line; print encode('utf8',$line); } close $FH;
無事、文字化けせずに表示ができました。
次回
1個のcsvファイルを処理するにはこれで良いと思うんですが、これが100個あったときにはファイル名を変えつつ実行しなくてはいけないのでは・・・?
心を込めて100回、ファイル名を変えるの?
・・・
ということで、次回はフォルダから中のファイルを拾う方法てのをやります。
おまけ
6年くらい前、Perl勉強してこ!ってときに使っていたのは Windows マシンだったのですが、Webから拾ってきた諸々が文字化けするのが解消できなかったんですね。
ですので、Macを買ったのでした。これぞ富豪的解決!・・・とはならず、今度は Windows から出力したファイルを mac で扱おうとして文字化けしてしまい、苦しみは尽きないのでした。
昨今、文字コードはアプリケーション側で吸収することも多く、文字コードの差異で苦しむのはプログラムを書くときくらいかも知れません。