나는 내가 찾고 있는 키워드의 전체 집합이 포함된 디렉터리의 모든 파일을 파일의 어느 위치에서나 나열하는 방법을 찾고 있습니다.
따라서 키워드가 같은 줄에 나타날 필요는 없습니다.
한 가지 방법은 다음과 같습니다.
grep -l one $(grep -l two $(grep -l three *))
3개의 키워드는 하나의 예일 뿐이며 2개 또는 4개 등이 될 수 있습니다.
제가 생각할 수 있는 두 번째 방법은 다음과 같습니다.
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}' *
같은 효과를 얻는 것이 더 낫기 때문에, 이를 허용하는 솔루션을 선호합니다.
답변1
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
이전 매개변수는 모두 --
검색 패턴으로 간주됩니다. 그 이후의 모든 인수는 --
파일 이름으로 처리됩니다. 이 작업에 대한 원시적이지만 효과적인 옵션입니다. 또는 모듈 -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
포함 파일에는 하나 이상의 완전한 패턴 세트가 포함되어야 하며, 여기서 각 멤버는
$_pstring
해시의 정렬 키를 포함하는 문자열입니다. 문자열에는 역시 해시에서 빌드된 $pattern
미리 컴파일된 정규식이 포함되어 있습니다 .%patterns
$pattern
각 입력 파일의 각 줄을 비교하고( 실행 중에 절대 변경되지 않으므로 수정자를 사용하여 한 번만 /o
컴파일 ) 각 파일에 대한 일치 항목을 포함하는 해시(%s)를 빌드하는 데 사용됩니다.$pattern
map()
$m_string
( )의 정렬 키 가 %s
동일한 지 비교하여 현재 파일에 모든 패턴이 표시될 때마다 $p_string
파일 이름을 인쇄하고 다음 파일로 이동합니다.
특별히 빠른 솔루션은 아니지만, 너무 느리지도 않습니다. 첫 번째 버전은 74MB 압축 로그 파일(비압축 총 937MB)에서 세 단어를 검색하는 데 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개 파일의 74MB 샘플에서). 어떤 경우에 유용하다면 명령줄 옵션(예: )으로 만들 수 있지만 -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(구글 검색으로 찾았습니다 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
전체 파일이 한 줄로 처리 .
되도록 레코드 구분 기호를 다음과 같이 설정합니다 .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
세 문자열이 모두 발견되면 Perl 스크립트는 상태(성공)로 종료됩니다. }{
Perl의 약어 입니다 END{}
. 모든 입력이 처리된 후에는 그 이후의 모든 항목이 실행됩니다. 따라서 모든 문자열을 찾을 수 없으면 스크립트는 0이 아닌 종료 상태로 종료됩니다. 따라서 && printf '%s\n' "$f"
세 개의 파일이 모두 발견된 경우에만 파일 이름이 인쇄됩니다.
또는 파일을 메모리에 로드하지 마세요.
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
또 다른 옵션 - 한 번에 한 단어씩 입력하고 파일에 대해 실행 되도록 합니다 . 호출이 실패를 반환하면 이를 반환하여 종료되도록 할 수 있습니다(문서 확인). 물론 이 솔루션과 관련된 셸 및 포크 생성으로 인해 작업 속도가 크게 느려질 수 있습니다.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