while ループによる File::Find::Norecursible

ガーベジコレクションやトポロジカルソートで散々書いてきたグラフのノード探索の応用として、Perl の添付モジュール Find::File の簡易版を書いてみます。

  • 再帰呼び出しせず、while ループで深さ優先探索をします。
  • 再帰呼び出ししていないので、croak で素直に呼び出し元を表示することができます。
  • ディレクトリへのシンボリック・リンクは探索をスキップします。
  • ドットで始まるエントリもスキップします。
  • 既に訪れたことのあるディレクトリへのハード・リンクもスキップします。探訪済みのディレクトリは inode 番号でマークしています。
  • 第二引数にコード・リファレンスを与えて、ファイルごとにコールバックします。
  • 戻り値に訪れたディレクトリのパスの配列リファレンスを返します。
#!/usr/bin/env perl

package File::Find::Nonrecursible;
use strict;
use warnings;
use File::stat;
use Carp;

our $VERSION = '0.001';

sub find {
    my($class, $basedir, $yield) = @_;
    my %mark;
    $basedir =~ s{(?!\A)/\z}{}msx;
    croak "find: cannot read dir '$basedir'." if ! (-d $basedir && -r _);
    my @stack = ($basedir);
    while (my $dir = shift @stack) {
        my $stat = stat $dir;
        my $inode = join q( ), $stat->dev, $stat->ino;
        next if $mark{$inode};
        $mark{$inode} = $dir;

        opendir my($dh), $dir or croak "find: cannot open dir '$dir'.";
        my @entry = sort map { m/\A[^.]/msx ? "$dir/$_" : () } readdir $dh;
        closedir $dh;

        my @dirs;
        for (@entry) {
            next if ! -r $_;
            if (-f _) {
                $yield->($_);
            }
            elsif (-d _ && -l $_) {
                push @dirs, $_;
            }
        }
        unshift @stack, @dirs;
    }
    return [values %mark];
}

package main;
use strict;
use warnings;
use feature qw(say);

my $dirs = File::Find::Nonrecursible->find('.', sub{ say });
say for @{$dirs};