查找文件中任意位置包含多個關鍵字的文件
我正在尋找一種方法來列出目錄中的所有文件,這些文件包含我正在尋找的完整關鍵字集,位於文件中的任何位置。
因此,關鍵字不必出現在同一行。
一種方法是:
grep -l one $(grep -l two $(grep -l three *))
三個關鍵字只是一個例子,它也可以是兩個或四個,等等。
我能想到的第二種方法是:
grep -l one * | xargs grep -l two | xargs grep -l three
出現在另一個問題中的第三種方法是:
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}' *
並獲得相同的效果會更好,所以我更喜歡允許這樣做的解決方案。
awk 'FNR == 1 { f1=f2=f3=0; }; /one/ { f1++ }; /two/ { f2++ }; /three/ { f3++ }; f1 && f2 && f3 { print FILENAME; nextfile; }' *
如果您想自動處理 gzip 壓縮文件,請在循環中執行它
zcat
(緩慢且低效,因為您將awk
在循環中多次分叉,每個文件名一次)或重寫相同的算法perl
並使用IO::Uncompress::AnyUncompress
庫模組,它可以解壓縮幾種不同類型的壓縮文件(gzip、zip、bzip2、lzop)。或者在 python 中,它也有處理壓縮文件的模組。這是一個允許任意數量的模式和任意數量的文件名(包含純文字或壓縮文本)的
perl
版本。IO::Uncompress::AnyUncompress
之前的所有參數
--
都被視為搜尋模式。之後的所有參數--
都被視為文件名。這項工作的原始但有效的選項處理。可以使用or模組實現更好的選項處理(例如,支持-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
在這裡……它們只包含部分或全部單詞“一”“二”“三”“四”“五”和“六”用於測試。上面輸出中列出的文件DO 包含所有三種搜尋模式。使用您自己的數據自行測試)#! /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
包含文件必須包含的完整模式集,每個成員中至少有一個$_pstring
是包含該散列的排序鍵的字元串。該字元串$pattern
包含一個預編譯的正則表達式,也是從%patterns
散列建構的。
$pattern
與每個輸入文件的每一行進行比較(使用/o
修飾符$pattern
只編譯一次,因為我們知道它在執行期間永遠不會改變),並map()
用於建構包含每個文件匹配項的雜湊 (%s)。每當在目前文件中看到所有模式時(通過比較 if
$m_string
(在 中的排序鍵%s
)是否等於$p_string
),列印文件名並跳到下一個文件。這不是一個特別快的解決方案,但也不是不合理的慢。第一個版本花了 4 分 58 秒在 74MB 的壓縮日誌文件中搜尋三個單詞(未壓縮的總共 937MB)。目前版本需要 1 分 13 秒。可能還可以進行進一步的優化。
一個明顯的優化是將它與
xargs
’-P
aka結合使用--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[@]}" --
通過該優化,只需 23 秒即可找到所有 18 個匹配文件。當然,任何其他解決方案都可以這樣做。注意:輸出中列出的文件名的順序會有所不同,因此可能需要事後進行排序。
正如@arekolek 所指出的,多個
zgrep
s 使用find -exec
orxargs
可以顯著加快速度,但該腳本的優勢在於支持任意數量的模式進行搜尋,並且能夠處理幾種不同類型的壓縮。如果腳本僅限於檢查每個文件的前 100 行,它會在 0.6 秒內執行所有文件(在我的 269 個文件的 74MB 範例中)。如果這在某些情況下有用,可以將其設置為命令行選項(例如
-l 100
),但存在找不到所有匹配文件的風險。順便說一句,根據手冊頁
IO::Uncompress::AnyUncompress
,支持的壓縮格式是:最後一個(我希望)優化。通過使用
PerlIO::gzip
模組(打包在 debian as 中libperlio-gzip-perl
)而不是IO::Uncompress::AnyUncompress
我將處理 74MB 日誌文件的時間縮短到大約3.1 秒。Set::Scalar
通過使用簡單的雜湊而不是(這也節省了幾秒鐘的IO::Uncompress::AnyUncompress
版本)也有一些小的改進。
PerlIO::gzip
在https://stackoverflow.com/a/1539271/137158中被推薦為最快的 perl gunzip (通過 google 搜尋找到perl fast gzip decompress
)使用
xargs -P
它根本沒有改善它。事實上,它甚至似乎將其減慢了 0.1 到 0.7 秒。(我嘗試了四次執行,我的系統在後台執行其他操作,這會改變時間)代價是這個版本的腳本只能處理 gzip 和未壓縮的文件。速度與靈活性:此版本為 3.1 秒,而帶包裝器的版本為 23 秒(或不
IO::Uncompress::AnyUncompress
帶包裝器的版本為xargs -P
1m13sxargs -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; } } }
將記錄分隔符設置為,
.
以便awk
將整個文件視為一行:awk -v RS='.' '/one/&&/two/&&/three/{print FILENAME}' *
與以下類似
perl
:perl -ln00e '/one/&&/two/&&/three/ && print $ARGV' *