Perlでリーフディレクトリを検出する

Perlでリーフディレクトリを検出する

私が開発しているPerlスクリプトについて、以下を探しています。早くそして信頼できる指定されたディレクトリ(遷移的)で葉であるすべてのサブディレクトリ、つまり独自のサブディレクトリがないサブディレクトリを見つける方法です。たとえば、次のような階層があるとします。

foo/
foo/bar/
foo/bar/baz
foo/you_fool

引数を使用して呼び出すと、私の仮想関数は"foo"listを返す必要があります("foo/bar/baz/", "foo/you_fool/")

File::Findこれは明らかにまたは同等のものが必要であり、すでに見つかったstatすべてのファイルに対してシステムコールを実行するため、早く各ファイルに対して追加の操作を実行してstatも、各ファイルに対して他の操作を実行しないことを意味します。stat目次つまり、値は$File::Find::dir大丈夫です。

私の主なターゲットシステムはDarwin(別名MacOS)なので、残念ながら;nlinkフィールドは使用できませんstruct stat。そのファイルシステムでは意味がないようです。私は「実際のUnix」ファイルシステムでnlink各ディレクトリを2と比較できることを知っています。

重要な場合は、シンボリックリンク、特殊ファイル、その他すべての奇妙な項目を無視できます。検索する階層は非常にきれいで規則的です。

答え1

次のことができます。

perl -MFile::Find -le '
  find(sub { 
         if (-d _) {
           undef $leaves{$File::Find::name};
           delete $leaves{$File::Find::dir};
         }
       }, ".");
  print for keys %leaves'

undef現在のディレクトリのハッシュ要素をundef値に設定し、delete親ディレクトリのハッシュ要素を削除します。したがって、最終ハッシュキーには%leavesリーフのみが含まれます。

の場合、現在のファイルで実行されている情報を再利用するため、-d _追加/操作は行われません。単独で追加が行われます。つまり、ディレクトリへのシンボリックリンクに対してもtrueを返します。lstat()File::Findlstat()stat()-dstat()lstat()

テストでは効果がありましたが、効率的で将来保証型ではない可能性があります。文書には次のように記載されています。

["follow"を含む]保証済み統計資料ユーザーの "wanted()" 関数を呼び出す前に呼び出されました。これにより、「_」関連ファイルをすばやく確認できます。次の場合、この保証は無効になります。フォローするまたはクイックフォロー設定なし

これはより安全ですが、各ファイルに対して追加の操作を実行するのはif (! -l && -d _)費用がかかります。lstat()

答え2

ちょうどいくつかの考え。私はPerlの専門家ではないので、File::Findが何ができるかわからないので、シェル「検索」に切り替えました。

find / -type d -print

「/」で始まるディレクトリのリストを印刷するので、これがデフォルトのリストです。 Cアプリケーションは可能ですが、Perlをより速くすることができるかどうかは非常に疑問です。マイナーな利益のためにエネルギーを無駄にするのではないかと疑われます。

GNU findには、親ディレクトリを印刷するために「%h」フラグを許可する「-printf」オプションがあります。したがって、あなたができることは、%pパスと親パス%hを同時に-printfしてから、親パスをPerlの新しいリストに分割することです。これで、葉ではなくパスのリストがあるので、%pリストからそのパスを削除すると操作が完了します。

残念ながら、MacOS用のGNUバージョンはなく、より低いバージョンしかありません。 'brew'を使用してGNU findをインストールできますが、%p行からPerlに直接%h効果を生成することはそれほど難しくありません。

最後に注意すべきことです。パイプまたは同様のパス名の改行終了に依存すると、場合によってはエラーが発生することが知られているため、GNU findとMacOS findはどちらも\ nではなく\ 0で区切られた行に対してゼロ終了オプションを提供します。利用できる場合は、そうしてください。

答え3

File::Find私が知らなかった、忘れてしまった機能を活用するので、思ったよりはるかに簡単です。以下は完全なスクリプトです(質問に関係のないコードを追加する前)。

#! /usr/bin/env perl

use warnings;
use strict;
use File::Find;
use Cwd qw(realpath);

@main::leaves = ();

sub preprocess {
  our (@leaves);
  my @names = @_;
  my @subdirs = grep { $_ ne q(.) && $_ ne q(..) && -d } @names;
  push @leaves, $File::Find::dir unless @subdirs;
  return @subdirs;
}

sub wanted {
  # do nothing at all
}

sub find_leaves {
  my @roots = map { realpath($_) } @ARGV;
  find({ wanted => \&wanted, preprocess => \&preprocess }, @roots);
}

sub main {
  our (@leaves);
  @ARGV or push @ARGV, q(.);
  find_leaves();
  print $_, qq(\n) foreach (@leaves);
  # my $num_leaves = $#leaves + 1;
}

main();
__END__

関連情報