File::Find - サブディレクトリを再帰的に処理する
File::Findを使用すると、再帰的にすべてのファイルを処理することができます。
use File::Find; find(\&process, $top_dir); sub process{ # 行いたい処理 }
File::Find は、chdir で、ディレクトリを変更しながら、すべてのファイルを処理していきます。第一引数には、サブルーチンへのリファレンスを渡します。( &process がサブルーチンで、 \ 記号で、リファレンスを作成しています。 )
第二引数以降は、処理したいディレクトリのリストを渡します。( 例では、ひとつだけ )。find の代わりに、 finddepth を使うと、走査順を、変更できます。( 以下で解説 )
カレントディレクトリを変更したくない場合
カレントディレクトリを変更しないで、走査したい場合は「no_chdir」オプションを使用することができます。
この場合は、サブルーチンへのリファレンスは「wanted」に指定します。
find({wanted => \&process, no_chdir => 1}, $top_dir);
処理の記述の中で、使うことのできる変数
現在のカレントディレクトリ | $File::Find::dir |
現在のファイル名( ベース名 ) | $_ |
現在のファイル名( 絶対パス ) | $File::Find::name |
sub process の中で、カレントディレクトリや、処理対象のファイル名を取得できます。これらを使って、各ファイルに行いたいことを記述します。
find の走査順序( 行きがけ順 )
(1) $top_dir |-- (2) top.txt | |-- (3) dir1 |-- (4) 1.txt | |-- (5) dir1_1 |-- (6) 1_1.txt | |-- (7) dir1_2 |-- (8) 1_2.txt | |-- (8) dir2 |-- (9) 2.txt
一般的に行きがけ順と呼ばれている順で、走査します。同一階層の場合は、ディレクトリより先に、ファイルが、処理されます。(3) より (2) のほうが先。
finddepth の走査順序(帰りがけ順)
(10) $top_dir |-- (1) top.txt | |-- (7) dir1 |-- (2) 1.txt | |-- (4) dir1_1 |-- (3) 1_1.txt | |-- (6) dir1_2 |-- (5) 1_2.txt | |-- (9) dir2 |-- (8) 2.txt
一般的に帰りがけ順と呼ばれている順で、走査します。各ディレクトリは、自分より下の階層の処理がすべて終了した直後に、処理されます。同一階層の場合は、ディレクトリより先に、ファイルが、処理されます。
サンプル
再帰的にすべてのファイルを処理するサンプルです。
use strict; use warnings; use File::Path; use Fcntl; use File::Find; # 再帰的にすべてのファイルを処理する。 # ディレクトリとファイルの作成 my $top_dir = "dir_20080530_$$"; my @dirs = ( "$top_dir/dir1", "$top_dir/dir1/dir1_1", "$top_dir/dir1/dir1_2", "$top_dir/dir2", ); for my $dir (@dirs) { eval { mkpath $dir }; if (@!) { die "@!" } } my @files = ( "$top_dir/top.txt", "$top_dir/dir1/1.txt", "$top_dir/dir1/dir1_1/1_1.txt", "$top_dir/dir1/dir1_2/1_2.txt", "$top_dir/dir2/2.txt" ); for my $file (@files) { sysopen( my $fh, $file, O_WRONLY | O_CREAT | O_EXCL ) or die "$file を作成することができません。: $!"; close $fh; } print "準備: $top_dir を作成しました。\n\n"; # File::Findのサンプル print "1: 再帰的に、すべてのファイル名( ベース名 )を出力する。\n"; # 第一引数に、それぞれのファイルに対して行いたい処理を書いた # サブルーチンへのリファレンスを渡す。 find( \&print_file_name, $top_dir ); sub print_file_name{ # $_ に、現在走査しているディレクトリを # 基点としたファイル名が格納されている。 # $File::Find::dir には、現在走査している # ディレクトリ名が、格納されている。 print "$_ ( $File::Find::dir )\n"; } print "\n"; print "2: 再帰的にすべてのファイル名(フルパス)を出力する。\n"; find(\&print_file_full_name, $top_dir); sub print_file_full_name{ # $_ に、現在走査しているディレクトリを # 基点としたファイル名が、格納されている。 print $File::Find::name, "\n"; } print "\n"; print "3: ディレクトリを、帰りがけ順に走査する\n"; finddepth(\&print_file_full_name, $top_dir);
実行結果
準備: dir_20080530_4944 を作成しました。 1: 再帰的に、すべてのファイル名( ベース名 )を出力する。 . ( dir_20080530_4944 ) top.txt ( dir_20080530_4944 ) dir1 ( dir_20080530_4944 ) 1.txt ( dir_20080530_4944/dir1 ) dir1_1 ( dir_20080530_4944/dir1 ) 1_1.txt ( dir_20080530_4944/dir1/dir1_1 ) dir1_2 ( dir_20080530_4944/dir1 ) 1_2.txt ( dir_20080530_4944/dir1/dir1_2 ) dir2 ( dir_20080530_4944 ) 2.txt ( dir_20080530_4944/dir2 ) 2: 再帰的にすべてのファイル名(フルパス)を出力する。 dir_20080530_4944 dir_20080530_4944/top.txt dir_20080530_4944/dir1 dir_20080530_4944/dir1/1.txt dir_20080530_4944/dir1/dir1_1 dir_20080530_4944/dir1/dir1_1/1_1.txt dir_20080530_4944/dir1/dir1_2 dir_20080530_4944/dir1/dir1_2/1_2.txt dir_20080530_4944/dir2 dir_20080530_4944/dir2/2.txt 3: ディレクトリを、帰りがけ順に走査する dir_20080530_4944/top.txt dir_20080530_4944/dir1/1.txt dir_20080530_4944/dir1/dir1_1/1_1.txt dir_20080530_4944/dir1/dir1_1 dir_20080530_4944/dir1/dir1_2/1_2.txt dir_20080530_4944/dir1/dir1_2 dir_20080530_4944/dir1 dir_20080530_4944/dir2/2.txt dir_20080530_4944/dir2 dir_20080530_4944
(参考)File::Path、eval,sysopen関数、Fcntl