Oct 27, 2011

Windows の Perl でテキストファイルの文字コードを変換

2011年11月12日追記:ぽち*ぷ〜ちいさましいちびのツールたち unzip.pl untar.pl enc.pl に enc.pl についてもう少しだけ詳しく書きました。

父の Vista機に Strawberry Perl をインストールして Windows上で Perl を使っていると、ぽちの Linux機で書いた日本語のテキストファイルを読みたい場合があったりします。けれども、それぞれのファイルシステムで使われている日本語文字コードが異なると、文字コードを変換しないままでは文字化けしてしまう事があります。高機能なテキストエディタがインストールしてあれば、わざわざ文字コードを変換したりしなくても読めるのですが、父の Vista機にはテキストエディタはメモ帳しか入っていません。メモ帳は Windows で使われている CP932以外にも UTF-8 ならそのまま表示出来るのですけども、日本語のメールで使われている ISO-2022-JP や、古いUNIX/Linux で使われている EUC-JP は文字コードを変換しないと読めません。

ものぐさなぽちは、文字コードや改行コードを自分で指定するのがめんどくさいので、unzip.pluntar.pl の文字コード自動推測部分を流用して、こんなのを書いて楽をしています。

enc.pl

#!/usr/bin/env perl

use strict;
use warnings;
use utf8;
use Encode;
use Encode::UTF8Mac; # utf-8-mac は fオプションと tオプションでしか使えません。
use Encode::Guess qw/ euc-jp cp932 iso-2022-jp utf8 /; # 自動推測の対象
use Encode::Locale;
use Path::Class;
use Getopt::Std;

my %opts;
getopts('f:t:', \%opts);

@ARGV or die "Usage: enc.pl [-f encoding_name] [-t encoding_name] text_file\n";

foreach ( @ARGV ) {
    my $file = file $_;
    my $reader = $file->openr or die "Couldn't open $file for reading: $!\n";
    my $content = $file->slurp;
    $reader->close;

    rename $file, "$file.bak";
    my ( $from, $to );
    if ( $opts{ f } ) {
        $from = $opts{ f };
    } else {
        my $f_obj = Encode::Guess->guess( $content );
        ref $f_obj ? $from = $f_obj->name : die "Couldn't guess: $f_obj\n";
    }
    if ( $opts{ t } ) {
        $to = $opts{ t };
    } else { $to = 'locale_fs'; }
    $content = encode( $to, decode( $from, $content ) );
    $from =~ /cp932|iso-2022-jp/ and $content =~ s/\r\n/\n/g;
    $to =~ /cp932|iso-2022-jp/ and $content =~ s/\n/\r\n/g;

    my $writer = $file->openw or die "Couldn't open $file for writing: $!\n";
    $writer->print( $content );
    $writer->close;
}

exit;
C¥> perl enc.pl [-f エンコード名] [-t エンコード名] テキストファイル

基本的には fオプションや tオプションも無くても適切に変換します。変換前の文字コードの推測に失敗してエラーが出た時には fオプションで、変換後の文字コードをファイルシステムのロケールにしたくない場合には tオプションで文字コードを指定して下さい。

一応、Mac で使われてる UTF-8-MAC も扱えますけども、UTF-8 と UTF-8-MAC の判別が難しいので、自動推測の候補には入れていません。UTF-8-MAC を扱う場合にはオプションで指定して下さい。

Posted at 18:09 in perl | Permalink | Comments/Trackbacks ()

Oct 22, 2011

Strawberry Perl 5.12.3.0 をインストール

2011年11月12日追記:ぽち*ぷ〜ちいさましいちびのツールたち unzip.pl untar.pl enc.pl に untar.pl についてもう少しだけ詳しく書きました。

Windows上で使う Perl に疎いぽちは、Perl で書いたコードを Windows上で実行可能な単独の exeファイルに纏める PARPAR::Packer というモジュールがある事を最近知りました。これらのモジュールは何も Windows の exe形式だけに限らず単独の実行形式ファイルを作るのに使えるのですけども、Windows上で Perl のコードから単独で実行可能な exeファイルを作るのは、Perl をインストールさせる事なく、Perl で書いたものを気軽に「これ使ってね♪」と渡せるという点ではとっても便利そうな気がします。

そこでぽちは、父が年賀状作成と孫たちの動画の保管や写真のプリントにしか使っていない、インターネット回線にさえ繋げる気が無い Windows Vista機に Strawberry Perl 5.12.3.0 を入れる許可を得て、インストールしてみました。Strawberry Perl自体は msi に纏められているので、オフラインでもぽちの Linux機でダウンロードしたものを持って来て簡単にインストール出来るのですけども、追加でインストールしたいモジュールは CPAN Search で個別に探してダウンロードして、それぞれ CPAN Dependencies で依存関係を確認しながら、必要なものから順序良く手動で入れるという作業をする事になってしまいました。

父の Vista機を回線に繋げられれば、わざわざこんな作業をしなくても cpanm なり、cpanm -f なりで済んでしまうんですけど、自分の PC じゃ無い以上「回線には繋げるな」という父の意向を無視するわけにはいきません。今時、私用の PC をスタンドアローンで使うなんていうセキュリティ確保の方法は現実的じゃない気がしますが…ぽちが言っても耳を貸してくれませんでした。

Vista機は数年前の購入時のままの状態で、Perlモジュールの圧縮形式、tar.gzファイルを展開するツールなんて入っていません。そこで入れたばっかりの Perl を利用してコマンドプロンプトから

C¥> perl -MArchive::Extract -e "$ae = Archive::Extract->new(archive => q/Path-Class-0.24.tar.gz/); $ae->extract;"

こんなふうにワンライナーで展開してみました。

これで充分用は足りるんですけども、ついでなのでぽちの unzip.plArchive::Tar で書き換えてこんな感じにしてみました。

untar.pl

#!/usr/bin/env perl

use strict;
use warnings;
use Archive::Tar;
use utf8;
use Encode;
use Encode::UTF8Mac; # utf-8-mac は fオプションでしか使えません。
use Encode::Guess qw/ euc-jp cp932 iso-2022-jp utf8 /; # 自動推測の対象
use Encode::Locale;
use Path::Class;
use Getopt::Std;

my %opts;
getopts('f:', \%opts);

@ARGV or die "Usage: untar.pl [-f encoding_name] tar_file\n";

foreach ( @ARGV ) {
    my $file = file $_;
    my $tar = Archive::Tar->new();
    $tar->read( "$file" ) or die "Couldn't read: $file\n";

    my $str;
    $str .= $_ foreach ( $tar->list_files );
    my $enc_name;
    if ( $opts{ f } ) {
        $enc_name = $opts{ f };
    } else {
        my $enc = Encode::Guess->guess( $str );
        ref $enc ? $enc_name = $enc->name : die "Couldn't guess: $enc\n";
    }

    foreach ( $tar->list_files ) {
        my $file = file( encode( 'locale_fs', decode( $enc_name, $_ ) ) );
        my $dir = $file->parent;
        -d $dir or dir( $dir )->mkpath;
        print "extract $file ...";
        $tar->extract_file( $_, "$file" );
        print "done.\n";
    }
}

exit;

見ての通り対象となるファイルのアーカイブ・圧縮形式が違うだけで、使い方は unzip.pl と同じです。

C¥> perl untar.pl [-f エンコード名] tarファイル

追加したモジュールですけども、文字コードcp932 の日本語版Windows上で Encode::Locale のテストの t/env.t が通らないのは有名みたいですね。

調べたらこんな感じだった。
env.t
> Encode::Locale::reinit("cp1252");
Windowsではコードページの変更が出来ない

> $ENV{"m\xf6ney"} = "\x80uro";
・コードページ"cp932"(日本標準)ではこの設定ができない
・なぜかPerlが落ちる
・コードページが元から"cp1252"(西欧標準)の場合は上手く行く

英語版のWindowsだと問題を把握できないかも。

626 - Perlについての質問箱 48箱目 - 2chプログラム技術板 より

とりあえずテストの失敗を無視して入れてもきちんと動いているようです。

Posted at 08:25 in perl | Permalink | Comments/Trackbacks ()

Oct 19, 2011

Perl で unzip.pl

2011年11月12日追記:ぽち*ぷ〜ちいさましいちびのツールたち unzip.pl untar.pl enc.pl に unzip.pl についてもう少しだけ詳しく書きました。

UNIX/Linuxzipファイルの展開に使われる unzip という有名な展開ツールがありますが、ファイルシステムのロケールに設定された文字コードが異なる環境で作成された zipファイルで、日本語ファイル名が含まれているものを展開すると、ファイル名が文字化けしてしまいます。C で書かれた unzip のソースコードに、マルチバイト文字のファイル名に対応するパッチを当てれば文字化けしないようになるのですけども、Perl大好きなぽちはずっと以前から、狐の王国の Suganoさんが書かれた unzip.pl をもとに、ぽちが使い易いようにちまちま改造を加えたものを zipファイルの展開に使っています。最近のぽちの unzip.pl はこんな感じです。

unzip.pl

#!/usr/bin/env perl

use strict;
use warnings;
use Archive::Zip qw/ :ERROR_CODES /;
use utf8;
use Encode;
use Encode::UTF8Mac; # utf-8-mac は fオプションでしか使えません。
use Encode::Guess qw/ euc-jp cp932 iso-2022-jp utf8 /; # 自動推測の対象
use Encode::Locale;
use Path::Class;
use Getopt::Std;

my %opts;
getopts('f:', \%opts);

@ARGV or die "Usage: unzip.pl [-f encoding_name] zip_file\n";

foreach ( @ARGV ) {
    my $file = file $_;
    my $zip = Archive::Zip->new();
    $zip->read( "$file" ) == AZ_OK or die "Couldn't read: $file\n";

    my $str;
    $str .= $_->fileName foreach ( $zip->members );
    my $enc_name;
    if ( $opts{ f } ) {
        $enc_name = $opts{ f };
    } else {
        my $enc = Encode::Guess->guess( $str );
        ref $enc ? $enc_name = $enc->name : die "Couldn't guess: $enc\n";
    }

    foreach ( $zip->members ) {
        my $file = file( encode( 'locale_fs', decode( $enc_name, $_->fileName ) ) );
        my $dir = $file->parent;
        -d $dir or dir( $dir )->mkpath;
        print "extract $file ...";
        $zip->extractMember( $_->fileName, "$file" );
        print "done.\n";
    }
}

exit;

使い方は、

$ ./unzip.pl [-f エンコード名] zipファイル

基本的に fオプションは必要なく、zipファイルの中のファイル名からエンコードされている文字コードを推測して、そのファイル名をファイルシステムのロケールに合わせて変換してから展開してくれます。たまに推測に失敗する時もありますが、そんな時はエラーメッセージを見て fオプションで適切な文字コードを指定してあげます。

Archive::ZipPOD を読んでみたら、「展開するだけなら Archive::Zip じゃなくて Archive::Extract を使ってね?」なんて書いてありますね。けど Archive::Extractって一度展開した後でないと内容物のファイル名を取得出来ないんで、文字コードの変換をするにはちょっと不便なんですよねえ。

Posted at 20:43 in perl | Permalink | Comments/Trackbacks ()