ファイルのどこでも複数のキーワードを含むファイルを探す

ファイルのどこでも複数のキーワードを含むファイルを探す

私が探しているキーワードの完全なセットを含むディレクトリ内のすべてのファイルをファイルのどこにでもリストする方法を探しています。

したがって、キーワードが同じ行に表示される必要はありません。

1つの方法は次のとおりです。

grep -l one $(grep -l two $(grep -l three *))

3つのキーワードは1つの例に過ぎず、2つまたは4つなどにすることができます。

私が考えることができる2番目の方法は次のとおりです。

grep -l one * | xargs grep -l two | xargs grep -l three

3番目の方法は次のとおりです。別の問題、する:

find . -type f \
  -exec grep -q one {} \; -a \
  -exec grep -q two {} \; -a \
  -exec grep -q three {} \; -a -print

しかし、それは確かにいいえ私が行きたい方向。私はタイピングが少なく、一度だけ呼び出す必要がありますgrepawkperl

たとえば、私は次の方法が好きです。awkすべてのキーワードを含む行を一致させることができます。、よい:

awk '/one/ && /two/ && /three/' *

またはファイル名を印刷してください。

awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *

しかし、キーワードが必ずしも同じ行にある必要はなく、ファイルのどこにでも存在できるファイルを探したいと思います。


好ましい解決策はgzipに優しいです。たとえば、圧縮ファイルに適したバリエーションがgrepあります。zgrepこれらの制限により、一部の解決策が正しく機能しない可能性があるため、これに言及します。たとえば、awk一致するファイルを印刷する例では、次のことはできません。

zcat * | awk '/pattern/ {print FILENAME; nextfile}'

次のようにコマンドを大幅に変更する必要があります。

for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done

したがって、制限によりawk圧縮されていないファイルの場合は、一度だけ呼び出すことができますが、複数回呼び出す必要があります。もちろんこのようにしてzawk '/pattern/ {print FILENAME; nextfile}' *同じ効果を得るのがより良いので、それを可能にする解決策を好みます。

ベストアンサー1

awk 'FNR == 1 { f1=f2=f3=0; };

     /one/   { f1++ };
     /two/   { f2++ };
     /three/ { f3++ };

     f1 && f2 && f3 {
       print FILENAME;
       nextfile;
     }' *

gzip圧縮ファイルを自動的に処理するには、ループで実行するかzcat(各ファイル名に対して1回ずつループを複数回分岐するため、遅くて非効率的ですawk)、同じアルゴリズムを再構築しperlIO::Uncompress::AnyUncompressライブラリモジュールを使用できます。さまざまな種類の圧縮ファイル(gzip、zip、bzip2、lzop)を解凍できます。または、Pythonには圧縮ファイルを処理するためのモジュールもあります。


これは、パターンとファイル名(通常または圧縮テキストを含む)を必要な数だけ受け入れるバージョンですperlIO::Uncompress::AnyUncompress

以前のパラメータはすべて--検索パターンと見なされます。それ以降のすべての引数は--ファイル名として扱われます。これは原始的ですが効果的なオプションです。または、モジュール-iを使用すると、Getopt::Stdより良いオプション処理(たとえば、大文字と小文字を区別しない検索オプションのサポート)を得ることができますGetopt::Long

次のように実行します。

$ ./arekolek.pl one two three -- *.gz *.txt
1.txt.gz
4.txt.gz
5.txt.gz
1.txt
4.txt
5.txt

(ここにファイルをリストしないでください。テストのために、「one」、「two」、「3」、「four」、「five」、および「six」という単語の一部または全部のみが含まれています。{1..6}.txt.gz{1..6}.txt

#! /usr/bin/perl

use strict;
use warnings;
use IO::Uncompress::AnyUncompress qw(anyuncompress $AnyUncompressError) ;

my %patterns=();
my @filenames=();
my $fileargs=0;

# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
  if ($_ eq '--') { $fileargs++ ; next };

  if ($fileargs) {
    push @filenames, $_;
  } else {
    $patterns{$_}=1;
  };
};

my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);

foreach my $f (@filenames) {
  #my $lc=0;
  my %s = ();
  my $z = new IO::Uncompress::AnyUncompress($f)
    or die "IO::Uncompress::AnyUncompress failed: $AnyUncompressError\n";

  while ($_ = $z->getline) {
    #last if ($lc++ > 100);
    my @matches=( m/($pattern)/og);
    next unless (@matches);

    map { $s{$_}=1 } @matches;
    my $m_string=join('',sort keys %s);

    if ($m_string eq $p_string) {
      print "$f\n" ;
      last;
    }
  }
}

ハッシュ%patternsインクルードファイルには、1つ以上の完全なパターンセットを含める必要があります。各メンバーは、 $_pstringハッシュのソートキーを含む文字列です。文字列には、ハッシュからビルドされた$patternプリコンパイルされた正規表現も含まれています。%patterns

$pattern各入力ファイルの各行を比較し(実行中は絶対に変更されないため、修飾子を使用して一度だけ/oコンパイル)、各ファイルの一致を含むハッシュ(%s)を構築するために使用されます。$patternmap()

$m_string()のソートキーが%s同じかどうかを比較し、現在のファイルにすべてのパターンが表示されるたびに$p_stringファイル名を印刷し、次のファイルに移動します。

特に速い解決策ではありませんが、遅すぎることはありません。最初のバージョンでは、74 MBの圧縮ログファイル(非圧縮合計937 MB)から3つの単語を取得するのに4分58秒かかりました。現在のバージョンは1分13秒かかります。更なる最適化が可能である。

xargs明確な最適化は、これを一緒に使用してファイルのサブセットに対して複数の検索を並列に実行すること-Pです。--max-procsこれを行うには、ファイル数を数え、システム内のコア/CPU/スレッド数で除算する必要があります(四捨五入するには1を追加する必要があります)。たとえば、私のサンプルセットから269個のファイルが検索され、私のシステムには6個のコア(AMD 1090T)があるため、次のようになります。

patterns=(one two three)
searchpath='/var/log/apache2/'
cores=6
filecount=$(find "$searchpath" -type f -name 'access.*' | wc -l)
filespercore=$((filecount / cores + 1))

find "$searchpath" -type f -print0 | 
  xargs -0r -n "$filespercore" -P "$cores" ./arekolek.pl "${patterns[@]}" --

この最適化では、一致する18個のファイルをすべて見つけるのに23秒しかかかりません。もちろん、他のソリューションを使用しても同じことができます。注:出力にリストされているファイル名の順序はさまざまであるため、重要な場合は後続のソートが必要になる場合があります。

@arekolekが指摘したように、orをzgrep使用すると複数のfind -execsを使用するxargsとより速く完了できますが、このスクリプトの利点は、必要な数のパターン検索をサポートし、さまざまな種類の圧縮を処理できることです。

スクリプトが各ファイルの最初の100行をチェックすることに制限されている場合は、すべてのファイルを0.6秒で実行します(269ファイルの74 MBサンプル)。場合によっては、コマンドラインオプション(たとえば)で作成できますが、-l 100見つからない危険があります。みんなファイルを一致させます。


ところで、マニュアルページによると、サポートされているIO::Uncompress::AnyUncompress圧縮形式は次のようになります。

  • 宿RFC 1950
  • 収縮RFC 1951(選択科目)、
  • アーカイブRFC 1952
  • 圧縮、
  • bzip2,
  • ジュープ,
  • lzf,
  • レズマ、
  • xz

最後に(希望)最適化。代わりPerlIO::gzipに、モジュール(debianなどのパッケージ化libperlio-gzip-perl)を使用してIO::Uncompress::AnyUncompress時間を大幅に短縮しました。3.1秒74MBのログファイルを処理するために使用されます。代わりに、単純なハッシュを使用すると、いくつかの小さな改善がありますSet::Scalar(このバージョンでは数秒が節約されます)。IO::Uncompress::AnyUncompress

PerlIO::gzip最速のPerl Gunzipとしておすすめhttps://stackoverflow.com/a/1539271/137158(Google検索で見つかりましたperl fast gzip decompress

使用してxargs -Pもまったく改善されませんでした。実際に見ると0.1~0.7秒ほど遅くなるようだった。 (4回も実行してみました。システムがバックグラウンドで別の作業を行っており、時間が変更されました。)

欠点は、このスクリプトバージョンがgzipで圧縮されたファイルと圧縮されていないファイルのみを処理できることです。速度と柔軟性:このバージョンの場合は3.1秒、ラッパー付きIO::Uncompress::AnyUncompressバージョンの場合は23秒xargs -P(またはラッパーのないバージョンの場合は1分13秒xargs -P)。

#! /usr/bin/perl

use strict;
use warnings;
use PerlIO::gzip;

my %patterns=();
my @filenames=();
my $fileargs=0;

# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
  if ($_ eq '--') { $fileargs++ ; next };

  if ($fileargs) {
    push @filenames, $_;
  } else {
    $patterns{$_}=1;
  };
};

my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);

foreach my $f (@filenames) {
  open(F, "<:gzip(autopop)", $f) or die "couldn't open $f: $!\n";
  #my $lc=0;
  my %s = ();
  while (<F>) {
    #last if ($lc++ > 100);
    my @matches=(m/($pattern)/ogi);
    next unless (@matches);

    map { $s{$_}=1 } @matches;
    my $m_string=join('',sort keys %s);

    if ($m_string eq $p_string) {
      print "$f\n" ;
      close(F);
      last;
    }
  }
}

おすすめ記事