本サイトではアフィリエイト広告を利用しています
perl-encode

コード

[perlの小ネタ] ディレクトリ内の日本語ファイル一覧取得

2020年4月8日

perl の小ネタです。
UNIX 使いにはなんてことない大量ファイルの名前変更ですが、Windows でエクスプローラなどを使うと動作が重くなりすぎて嫌になることがあります。
そんなときはコマンドプロンプトでやればいいだけですが、ファイルの中身を調べて(例えばヘッダを読んで)なんて作業を挟まなければならない時はメンドウです。perl で数行書いて処理してしまう方が楽だと考える初心者は、是非参考にしてください。

ここでは Windows で perl が使える環境があることを前提とします。まだ perl をインストールしていない方は、Windows 用の perl をわざわざダウンロードしなくても、大半は Cgwin にあるもので足りますので、必要な perl モジュールだけ組み込んでおいてください。
前半は小ネタですが、後半は文字コードが絡むので、面倒な点が出てきます。

Perl の glob でディレクトリを読む

まず、ディレクトリ一覧を表示させるための perl のコードです。

 
#!/usr/local/bin/perl
use utf8;
use strict;
use warnings;
print ".jpg と .png ファイル一覧を取得\n";
my @jpg_png_list = glob "*.jpg *.png";
print join("\n", @jpg_png_list) . "\n\n";

glob "*" とやれば、すべてのファイルをリスト化しますが、リネームなどが目的の時は特定の拡張子だけをターゲットにすることが多いと思うので、その場合はスペース区切りで上の例のように記述します。
3行目の「use utf8;」はスクリプトは文字コード UTF8 で書いていることを perl に伝えるおまじないです。最近の OS は UFT8 が好まれるのでそれに従っておきます。日本語ファイル名を処理しない、日本語の出力をしない場合は Shift JIS などの文字コードでも問題なく動きます。
配列 @jpg_png_list の要素にファイル名が一つずつの入るので、あとはそれを一つずつ取り出せば良いだけです。今回は forforeach を使わずに、そのまま配列を連結して、一気に出力します。
join は配列を連結する関数で、第一引数にセパレータを指定できます。ここでは改行を指定して、配列の中身を改行を挟んで連結しています。

readdir でディレクトリを読む

もう一つ、よく使う方法としては、ディレクトリハンドルを opendir 関数でオープンし、readdir 関数でディレクトリの読出す方法です。

 
#!/usr/local/bin/perl
use utf8;
use strict;
use warnings;
opendir my $dh, "." or die "Can't open directory: $!";
# ディレクトリ内部のすべてのファイルを読み込む
while (my $file = readdir $dh){
	print "$file\n";
}

perl のドキュメント通りのサンプルですが、Windows で perl を使って二バイト文字を含むディレクトリを読み出すときは、こちらの読み出しの方がうまくいくことが多いです。
拡張子をさっさと指定して読み出すときは glob 関数を使いがちですが、こちらの方もメモしておきましょう。

Wide character in print の対応

ここからは主に Windows 系の OS で日本語ファイル名などを処理するときに必ず出くわす問題です。
上のコードをそのまま実行すると、

 cygwin
Wide character in print at filelist.pl line 7.

とラインの何行目かに警告が出ることがあります。
この警告は、UTF8 で文字列を出力しようとしているのに、UTF8 フラグが立っていない場合に起きる、よくやる手抜きの時にありがちです。
この場合の Perl は、文字列とは確信を持たずに、バイナリかもと疑いつつ画面に出力していることになります。
無視しても実用上は問題ないのですが、気になるので、UTF8 フラグをつけておきます。

手っ取り早い解消法は、print 文が登場するより前に一度だけ以下のコードを入れておくことです。

 
#IO layer を UTF-8 に
binmode(STDOUT, ":utf8");

これは、画面に(標準出力に)変換などのプロセスを挟まず、そのまま内部処理コードのまま出力するモードになります。
しかし、この方法だと、内部処理コードとシステムの標準コードが異なる場合はファイルリストが文字化けを起こしてしまいます。
特に、Windows 環境だとこの影響がモロに出ます。
何が問題かと言えば、システムの方は取り合えずそれらしきデータを出力しているつもりですが、それが文字列かただのバイナリ配列かの区別ができていません。つまり、一文字一文字のデータの区切りがわからないため、それをしっかりつけてやるのが UTF8 フラグと言うことになります。

モジュール "Encode" を使って処理する

普通にファイルリストを出すだけなら特に難しいことはありませんが、"Encode" 処理が入ると、今扱っているデータはバイナリなのか文字列なのかをしっかり意識して処理していかないと、わけがわからなくなるので要注意です。

ここでは、比較的簡単な Encode についてくる Encode::Guess というモジュールで文字コードを推測してみます。

 
#!/usr/local/bin/perl
use utf8;
use strict;
use warnings;
use Encode;
use Encode::Guess qw/euc-jp shiftjis 7bit-jis/;
#IO layer を UTF-8 にする
binmode(STDOUT, ":utf8");
print "カレントディレクトリのファイル一覧を取得する。\n";
# * は、. で始まらないすべてのファイルを表現
my @file_list = glob "*";
print decode('Guess', join("\n", @file_list) . "\r\n");

IO layer を UTF-8 にするのは、スクリプト内に書いた日本語出力を画面に表示する際に Wide character の警告を出さないためです。
ポイントは decode('Guess', 文字列) のところで、第一引数に 'Guess' を与えているところです。
第一引数が 'cp932''utf8' 等にするとうまく行かないことがよくあります(逆にうまく行くこともあります)。うまくいく方法で処理しましょう。


関連記事
Cygwin
Windows に Cygwin をセットアップする入門編

Cygwin は Windows 上で Linux ディストリビューションと同様の機能が使えるようにする、オープンソース ...

続きを見る

-コード
-, ,