ファイル内の任意の場所にある、探しているキーワードの完全なセットを含むディレクトリ内のすべてのファイルを一覧表示する方法を探しています。
したがって、キーワードが同じ行に表示される必要はありません。
これを行う1つの方法は次のとおりです。
grep -l one $(grep -l two $(grep -l three *))
3つのキーワードは単なる例であり、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
しかし、これは間違いなく私がここに向かっている方向ではありません。入力が少なくて済み、grep
、awk
、Perl
などへの呼び出しが1回だけ必要なものが必要です。
たとえば、次のように 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}' *
と同じ効果が得られるので、これを可能にするソリューションを選びます。
これまでに提案されたすべてのソリューションの中で、grepを使用した私のオリジナルのソリューションは最速で、25秒で終了します。欠点は、キーワードを追加したり削除したりするのが面倒なことです。そこで、動作をシミュレートしながら、構文を変更できるスクリプト(multi
と呼ばれる)を思い付きました。
#!/bin/bash
# Usage: multi [z]grep PATTERNS -- FILES
command=$1
# first two arguments constitute the first command
command_head="$1 -le '$2'"
shift 2
# arguments before double-dash are keywords to be piped with xargs
while (("$#")) && [ "$1" != -- ] ; do
command_tail+="| xargs $command -le '$1' "
shift
done
shift
# remaining arguments are files
eval "$command_head $@ $command_tail"
したがって、multi grep one two three -- *
を書くことは、私の最初の提案と同等であり、同時に実行されます。代わりにzgrep
を最初の引数として使用することで、圧縮ファイルでも簡単に使用できます。
また、Pythonスクリプトを使用して、すべてのキーワードを行ごとに検索することと、ファイル全体をキーワードごとに検索することの2つの戦略を使用してスクリプトを試しました。2番目の戦略は、私の場合より高速でしたが、より低速でしたgrep
を使用するだけではなく、33秒で終了します。行ごとのキーワードマッチングは60秒で終了します。
#!/usr/bin/python3
import gzip, sys
i = sys.argv.index('--')
patterns = sys.argv[1:i]
files = sys.argv[i+1:]
for f in files:
with (gzip.open if f.endswith('.gz') else open)(f, 'rt') as s:
txt = s.read()
if all(p in txt for p in patterns):
print(f)
terdonによって与えられたスクリプト は54秒で終了しました。私のプロセッサーはデュアルコアなので、実際には39秒のウォールタイムがかかりました。私のPythonスクリプトは49秒のウォールタイムを必要としました(そしてgrep
は29秒でした)。
cas by cas は、4秒未満のgrep
で処理されたファイルの数が少ない場合でも、適切な時間で終了できなかったため、強制終了する必要がありました。
しかし、彼の最初のawk
提案は、そのままではgrep
より遅いにもかかわらず、潜在的な利点があります。場合によっては、少なくとも私の経験では、すべてのキーワードがファイル内にある場合、すべてがファイルの先頭のどこかにあると期待することができます。これにより、このソリューションのパフォーマンスが劇的に向上します。
for f in *; do
zcat $f | awk -v F=$f \
'NR>100 {exit} /one/ {a++} /two/ {b++} /three/ {c++} a&&b&&c {print F; exit}'
done
25秒ではなく、1/4秒で終了します。
もちろん、ファイルの先頭近くにあることがわかっているキーワードを検索する利点はありません。そのような場合、NR>100 {exit}
なしのソリューションには63秒かかります(実時間の50秒)。
私のgrep
ソリューションとcas 'awk
プロポーザルの間の実行時間に大きな違いはありません。どちらも実行に数秒かかります。
変数の初期化FNR == 1 { f1=f2=f3=0; }
は、後続のすべての処理済みファイルのカウンターをリセットする場合に必須です。そのため、このソリューションでは、キーワードを変更したり、新しいキーワードを追加したりする場合に、3つの場所でコマンドを編集する必要があります。一方、grep
を使用すると、| xargs grep -l four
を追加するか、必要なキーワードを編集できます。
コマンド置換を使用するgrep
ソリューションの欠点は、チェーンのどこかに、最後のステップの前に一致するファイルがない場合にハングすることです。 xargs
がゼロ以外のステータスを返すとパイプが中止されるため、これはgrep
バリアントには影響しません。 xargs
を使用するようにスクリプトを更新したので、これを自分で処理する必要がないため、スクリプトが簡単になりました。
_awk 'FNR == 1 { f1=f2=f3=0; };
/one/ { f1++ };
/two/ { f2++ };
/three/ { f3++ };
f1 && f2 && f3 {
print FILENAME;
nextfile;
}' *
_
Gzip圧縮されたファイルを自動的に処理する場合は、zcat
を使用してループでこれを実行します(ループ内でawk
を複数回フォークするため、ファイル名ごとに1回)、低速で非効率的です。同じアルゴリズムをPerl
で書き換え、_IO::Uncompress::AnyUncompress
_ライブラリモジュールを使用して、さまざまな種類の圧縮ファイル(gzip、Zip、bzip2、lzop)を解凍できます。または、圧縮ファイルを処理するためのモジュールもあるpythonで。
以下は、Perl
バージョンで、_IO::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
_
(ここでは、ファイル__{1..6}.txt.gz
_および_{1..6}.txt
_はリストしません...「1」「2」「3」「4」「5」および「6」という単語の一部またはすべてが含まれているだけですテスト用。上記の出力にリストされているファイルには、3つの検索パターンがすべて含まれています。独自のデータでテストしてください)
_#! /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
_修飾子を使用して_$pattern
_を1回だけコンパイルして、実行中に変更されないことがわかっているため)、およびmap()
は、各ファイルの一致を含むハッシュ(%s)を構築するために使用されます。
現在のファイルですべてのパターンが確認された場合は常に(_$m_string
_(_%s
_のソート済みキー)が_$p_string
_と等しいかどうかを比較して)、ファイル名を出力し、次のファイルにスキップします。
これは特に高速なソリューションではありませんが、不当に遅いわけではありません。最初のバージョンでは、74MB相当の圧縮ログファイル(合計937MB非圧縮)で3ワードを検索するのに4分58秒かかりました。この現在のバージョンには1分13秒かかります。おそらく、さらに最適化を行うことができます。
明らかな最適化の1つは、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で指摘されているように、_find -exec
_またはzgrep
を指定した複数のxargs
sを使用すると、処理が大幅に速くなりますが、このスクリプトには、検索するパターンをいくつでもサポートできるという利点があります。複数の異なるタイプの圧縮を処理することができます。
スクリプトが各ファイルの最初の100行のみを検査するように制限されている場合、スクリプトはそれらすべてを(私の74MBの269ファイルのサンプルで)0.6秒で実行します。これが役立つ場合は、コマンドラインオプション(_-l 100
_など)にすることもできますが、all一致するファイルが見つからないというリスクがあります。
ところで、_IO::Uncompress::AnyUncompress
_のマニュアルページによると、サポートされている圧縮形式は次のとおりです。
最後の(私は願っています)最適化。 _PerlIO::gzip
_の代わりに_libperlio-gzip-Perl
_モジュール(debianに_IO::Uncompress::AnyUncompress
_としてパッケージ化)を使用することで、処理時間を約3.1秒に短縮しました74MBのログファイル。 _Set::Scalar
_ではなく単純なハッシュを使用することにより、いくつかの小さな改善も行われました(これは、_IO::Uncompress::AnyUncompress
_バージョンでも数秒節約されました)。
_PerlIO::gzip
_は https://stackoverflow.com/a/1539271/137158 (_Perl fast gzip decompress
_のグーグル検索で見つかりました)の最速のPerl gunzipとして推奨されました
これで_xargs -P
_を使用しても、まったく改善されませんでした。実際には、0.1秒から0.7秒の範囲で速度が低下するように見えました。 (私は4つの実行を試みました、そして私のシステムはタイミングを変えるバックグラウンドで他のものをします)
価格は、このバージョンのスクリプトはgzip圧縮された非圧縮ファイルのみを処理できることです。速度と柔軟性:このバージョンでは3.1秒、_IO::Uncompress::AnyUncompress
_ラッパーを使用した_xargs -P
_バージョン(または_xargs -P
_を使用しない1m13s)では23秒。
_#! /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;
}
}
}
_
レコードセパレーターを.
に設定して、awk
がファイル全体を1行として扱うようにします。
awk -v RS='.' '/one/&&/two/&&/three/{print FILENAME}' *
同様にPerl
を使用:
Perl -ln00e '/one/&&/two/&&/three/ && print $ARGV' *
圧縮ファイルの場合、各ファイルをループして最初に解凍できます。次に、他の回答を少し変更したバージョンで、次のことができます。
for f in *; do
zcat -f "$f" | Perl -ln00e '/one/&&/two/&&/three/ && exit(0); }{ exit(1)' &&
printf '%s\n' "$f"
done
3つの文字列がすべて見つかった場合、Perlスクリプトは0
ステータス(成功)で終了します。 }{
は、END{}
のPerl省略形です。それ以降のすべては、すべての入力が処理された後に実行されます。したがって、すべての文字列が見つからなかった場合、スクリプトは0以外の終了ステータスで終了します。したがって、&& printf '%s\n' "$f"
は、3つすべてが見つかった場合にのみファイル名を出力します。
または、ファイルをメモリに読み込まないようにするには:
for f in *; do
zcat -f "$f" 2>/dev/null |
Perl -lne '$k++ if /one/; $l++ if /two/; $m++ if /three/;
exit(0) if $k && $l && $m; }{ exit(1)' &&
printf '%s\n' "$f"
done
最後に、スクリプトですべてを行いたい場合は、次のようにできます。
#!/usr/bin/env Perl
use strict;
use warnings;
## Get the target strings and file names. The first three
## arguments are assumed to be the strings, the rest are
## taken as target files.
my ($str1, $str2, $str3, @files) = @ARGV;
FILE:foreach my $file (@files) {
my $fh;
my ($k,$l,$m)=(0,0,0);
## only process regular files
next unless -f $file ;
## Open the file in the right mode
$file=~/.gz$/ ? open($fh,"-|", "zcat $file") : open($fh, $file);
## Read through each line
while (<$fh>) {
$k++ if /$str1/;
$l++ if /$str2/;
$m++ if /$str3/;
## If all 3 have been found
if ($k && $l && $m){
## Print the file name
print "$file\n";
## Move to the net file
next FILE;
}
}
close($fh);
}
上記のスクリプトをfoo.pl
として$PATH
のどこかに保存し、実行可能にして、次のように実行します。
foo.pl one two three *
別のオプション-単語を1つずつxargs
にフィードして、ファイルに対してgrep
を実行します。 xargs
は、255
を返すことにより、grep
の呼び出しが失敗を返すとすぐに、それ自体を終了させることができます(xargs
のドキュメントを確認してください)。もちろん、このソリューションに含まれるシェルとフォークの生成は、かなり遅くなる可能性があります
printf '%s\n' one two three | xargs -n 1 sh -c 'grep -q $2 $1 || exit 255' _ file
そしてそれをループする
for f in *; do
if printf '%s\n' one two three | xargs -n 1 sh -c 'grep -q $2 $1 || exit 255' _ "$f"
then
printf '%s\n' "$f"
fi
done