私が探しているキーワードの完全なセットを含むディレクトリ内のすべてのファイルをファイルのどこにでもリストする方法を探しています。
したがって、キーワードが同じ行に表示される必要はありません。
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
しかし、それは確かにいいえ私が行きたい方向。私はタイピングが少なく、一度だけ呼び出す必要がありますgrep
。awk
perl
たとえば、私は次の方法が好きです。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
)、同じアルゴリズムを再構築し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
(ここにファイルをリストしないでください。テストのために、「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)を構築するために使用されます。$pattern
map()
$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 -exec
sを使用するxargs
とより速く完了できますが、このスクリプトの利点は、必要な数のパターン検索をサポートし、さまざまな種類の圧縮を処理できることです。
スクリプトが各ファイルの最初の100行をチェックすることに制限されている場合は、すべてのファイルを0.6秒で実行します(269ファイルの74 MBサンプル)。場合によっては、コマンドラインオプション(たとえば)で作成できますが、-l 100
見つからない危険があります。みんなファイルを一致させます。
ところで、マニュアルページによると、サポートされているIO::Uncompress::AnyUncompress
圧縮形式は次のようになります。
最後に(希望)最適化。代わり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;
}
}
}
答え2
ファイル全体が1行として処理さ.
れるように、レコード区切り文字を次のように設定します。awk
awk -v RS='.' '/one/&&/two/&&/three/{print FILENAME}' *
同様にperl
:
perl -ln00e '/one/&&/two/&&/three/ && print $ARGV' *
答え3
圧縮ファイルの場合は、各ファイルを繰り返して最初に解凍できます。その後、他の回答を少し変更することで、次のことができます。
for f in *; do
zcat -f "$f" | perl -ln00e '/one/&&/two/&&/three/ && exit(0); }{ exit(1)' &&
printf '%s\n' "$f"
done
0
3つの文字列がすべて見つかると、Perlスクリプトはステータス(成功)で終了します。}{
Perlの略語ですEND{}
。すべての入力が処理された後、それ以降のすべての項目が実行されます。したがって、すべての文字列が見つからない場合、スクリプトはゼロ以外の終了状態で終了します。したがって、&& 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 *
答え4
xargs
別のオプション - 一度に1単語ずつ入力し、ファイルに対して実行するようにします。呼び出しが失敗を返した場合は、それを返して終了することができます(文書の確認)。もちろん、このソリューションに関連するシェルとフォークの作成により、作業速度が大幅に遅くなる可能性があります。grep
xargs
grep
255
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