ファイルがリストされているディレクトリにファイルを見つける方法は?

ファイルがリストされているディレクトリにファイルを見つける方法は?

ディレクトリには複数のサブディレクトリがあります。たとえば、次のようになります。

TopLevelDir
+-- SubDir1
+-- SubDir2
+-- SubDir3
+-- SubDir4
+-- SubDir5
+-- SubDir6

これらのサブディレクトリの特定のサブセットで特定のテキストを含むファイルを見つける必要があります。必要なものは次のことで達成できます。

find SubDir1 SubDir2 SubDir4 -type f -exec grep -H "desired text" {} \;

同様の検索が必要な場合がよくあります。時間と入力時間を節約するために、検索するサブディレクトリの名前をファイルに保存し、次にfindコマンドを実行するときに次のように使用したいと思います。

find subdirs2search.txt -type f  -name="*.txt" -exec grep -H "desired text" {} \;

ウェブを検索し、マンページをチェックして、私の*nixの本をチェックしても、これを行う方法についての情報はまったくありませんでした。

可能ですか?では、どうすればよいですか?

答え1

ディレクトリ名が1行に1つずつある場合はreadarray(bash v4 +)、名前にスペース、タブ、またはワイルドカードを含むディレクトリの問題を回避できます。

readarray -t dirs < subdirs2search.txt
find "${dirs[@]}" ...

一部のディレクトリ名がで始まる場合はまだ役に立ちませんが、-GNUではfindこの問題を解決する方法はありません。

答え2

検索がテキストファイルに限定されないことがわかりました。

ack再帰的なgrepタイプの作業に便利なツールであることがよくあります。それするデフォルトでは、検索はテキストファイル(ファイル名と内容に基づいてヒューリスティックを使用して決定されます)に制限され、.git/などのディレクトリはデフォルトでスキップされます.svn。これは、おそらく開発者が望むものです。 https://beyondgrep.com/

ほとんどのGNU / Linuxディストリビューションにはこの機能が含まれており、インストールが簡単です。これはPerlで書かれています(したがって、正規表現はperlGNUの正規表現に似た正規表現ですgrep -P)。

ack -- "desired text"  $(<subdirs.txt)

必要なことを行い、入力しやすくする必要があります。また、インタラクティブな使用に適したカラー出力を提供します。

(コマンドラインでトークン化を実行するさまざまな方法は、他の回答で説明されています。subdirs.txtシェルの標準トークン化がこれを行うようにしたり、行でreadarrayトークン化を実行したり、グローバル拡張を防止したりすることもできます。)

答え3

もちろん、この質問を投稿することで、これを厳密に実行する必要があるという執着を克服するのに役立ち、findBashを介してファイルを拡張することを考えました。他の人に役立つことを願って答えを投稿しています(後で使用できるように文書化しました)。

Bashがファイルの内容を拡張する注文は$(<subdirs2search.txt)。したがって、subdirs2search.txtに次のものが含まれている場合:

SubDir1 SubDir2 SubDir4

次のコマンドを使用すると、必要な検索が実行されます。

find $(<subdirs2search.txt) -type f -name="*.txt" -exec grep -H "desired text" {} \;

答え4

#!/usr/bin/perl -w

use strict;
use File::Find ();

sub wanted;
sub process_file ($@);

my $dirfile = shift;    # First argument is the filename containing the list
                        # of directories.

my $pattern = shift;    # Second arg is a perl RE containing the pattern to search
                        # for. Remember to single-quote it on the command line.

# Read in the @dirs array from $dirfile
#
# A NUL-separated file is best, just in case any of the directory names
# contained line-feeds.  If you're certain that could never happen, a
# plain-text LF-separated file would do.
#
# BTW, you can easily generate a NUL-separated file from the shell with:
#    printf "%s\0" dir1 dir2 dir3 dir4 $'dir\nwith\n3\nLFs' > dirs.txt

my @dirs=();

{
  local $/="\0";    # delete this line if you want to use a LF-separated file.
                    # In that case, the { ... } block around the code from open to
                    # close is no longer needed.  It's only there so it's possible
                    # to make a local change to the $/ aka $INPUT_RECORD_SEPARATOR
                    # variable.

  open(DIRFILE,"<",$dirfile);
  while(<DIRFILE>) {
    chomp;
    push @dirs, $_;
  };
  close(DIRFILE);
};

File::Find::find({wanted => \&wanted}, @dirs);
exit;

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && process_file($_);
}

sub process_file ($@) {

    # This function currently just greps for pattern in the filename passed to
    # it. As the function name implies, it could be used to process the file
    # in any way, not just grep it.

    my $filename = shift;

    # uncomment the return statement below to skip "binary" files.
    # (note this is a workable but fairly crude test.  Perl's File::MMagic
    # module can be used to more accurately identify file types, using the
    # same "magic" file databases as the /usr/bin/file command)

    # return if -B $filename;

    open(FILE,"<",$filename);
    while(<FILE>) {
      print "$filename:$_" if (m/$pattern/o) ;
    };

    close(FILE);
}

これはperlPerlFile::Findモジュールを使用して行われますfind ... -exec grep

このスクリプトには特に興味深いものや特別なものはありません。とは別にこのprocess_file機能は、所有者または権限の変更、ファイルの削除、名前の変更、行の挿入または削除、その他の目的の操作など、ファイルに対して必要な操作を実行するように簡単に変更できます。

たとえば、パターンに一致するテキストを含むファイルを削除するには、process_file関数を次のように置き換えることができます。

sub process_file ($@) {

    my $filename = shift;
    my $found = 0;

    # uncomment to skip "binary" files:
    return if -B $filename;

    open(FILE,"<",$filename);
    while(<FILE>) {
      if (m/$pattern/o) {
        $found = 1;
        last;
      };
    };

    close(FILE);
    unlink $filename if ($found);
}

wantedまた、このスクリプトの関数は現在、通常のファイル(-fテスト)のみを検索することにも言及する価値があります。 Perlstatlstat関数は、findファイルの一致に使用できるすべてのファイルメタデータ(uid、gid、perms、サイズ、atime、mtimeなど)へのアクセスを提供するため、関数wantedはすべてのルックアップ条件を複製できます。詳しく見て知っperldoc -f statてくださいperldoc -f lstat

ところで、このスクリプトは元のfind2perla)ファイルからディレクトリのリストを読み、b)フォークを介してgrepする代わりにperlコードからgrepし、grepc)多くのコメントを追加するように作成され、大幅に修正されました。find ... -exec grepgrepはPerlよりも速くファイルを開くことも、正規表現パターンマッチングを実行することもできないため、パフォーマンスはほぼ同じでなければなりません。より速いかもしれません。

さらに、find2perlこれはPerlに含まれていましたが、Perl 5.22から削除され、CPANで使用できるようになりました。2パールを探す

関連情報