web-dev-qa-db-ja.com

ファイルにリストされているディレクトリでファイルを見つけるにはどうすればよいですか?

ディレクトリには、次のようないくつかのサブディレクトリがあります。

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" {} \;

Webの検索、manページのチェック、そして私の* nixブックのチェックでさえ、これを行う方法について何も明らかにしていません。

これは可能ですか?もしそうなら、それをどのように行うことができますか?

3
GreenMatt

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

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

それでも、一部のディレクトリ名が-で始まる場合は役に立ちませんが、GNU findで始まる場合、それを回避する方法はありません。

8
Jeff Schaller

当然、質問を投稿することで、findを使用してこれを厳密に実行することに固執することがなくなり、Bash経由でファイルを拡張することを考えさせられました。私はそれが他の誰かの助けになることを願って(そしてまた私自身の将来の使用のためにこれを文書化するために)回答を投稿しています。

Bashにファイルの内容を展開させるための呪文は$(<subdirs2search.txt)です。したがって、subdirs2search.txtに以下が含まれている場合:

SubDir1 SubDir2 SubDir4

次のようなコマンドは、目的の検索を実行します。

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

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

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

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

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

おそらくあなたがやりたいことをすべきで、タイプするのはとても簡単です。さらに、インタラクティブな使用のための素晴らしいカラー出力を行います。

(コマンドラインにsubdirs.txtを単語分割するさまざまな方法は、他の回答で説明されています。シェルの標準の単語分割を実行させるか、readarrayを行のみに分割して、グロブの展開もブロックします。)

5
Peter Cordes
_#!/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);
}
_

これはPerlおよびPerlの_File::Find_モジュールを使用して、_find ... -exec grep_と同じことを行います。

このスクリプトについて特に興味深いものや特別なものはありませんexcept _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_テスト)。 Perlのstat関数とlstat関数は、findがファイルの一致に使用できるすべてのファイルメタデータ(uid、gid、perms、size、atime、mtimeなど)へのアクセスを提供するため、wanted関数はANYおよびすべての検索述語を複製できます。詳細については、_perldoc -f stat_および_perldoc -f lstat_を参照してください。

ところで、スクリプトは最初に_find2Perl_によって生成され、次に大幅に変更されてa)ファイルからディレクトリのリストを読み取り、b)grepをフォークするのではなく、Perlコードでgrepを実行しますc)追加コメントがたくさん。パフォーマンスは_find ... -exec grep_とほぼ同じであるはずです。これは、grepがファイルを開いたり、正規表現のパターンマッチングをPerlよりも大幅に高速に実行できないためです。それはさらに速いかもしれません。

また、BTW、_find2Perl_はPerlに含まれていましたが、Perl 5.22以降は削除され、CPANの find2Perl にあります。

3
cas

ack(Peter Cordesの答え)に似た別のツールは ripgrep またはrgです。同じ使い方をサポートします:

rg -- "desired text" $(<subdirs2search.txt)

また、デフォルトではバイナリファイルをスキップします。

3
Mark Anderson