파일 어디에서나 여러 키워드가 포함된 파일 찾기

파일 어디에서나 여러 키워드가 포함된 파일 찾기

나는 내가 찾고 있는 키워드의 전체 집합이 포함된 디렉터리의 모든 파일을 파일의 어느 위치에서나 나열하는 방법을 찾고 있습니다.

따라서 키워드가 같은 줄에 나타날 필요는 없습니다.

한 가지 방법은 다음과 같습니다.

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.awkperl

예를 들어, 나는 다음과 같은 방법을 좋아합니다.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)를 빌드하는 데 사용됩니다.$patternmap()

$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 -execs를 사용 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또 다른 옵션 - 한 번에 한 단어씩 입력하고 파일에 대해 실행 되도록 합니다 . 호출이 실패를 반환하면 이를 반환하여 종료되도록 할 수 있습니다(문서 확인). 물론 이 솔루션과 관련된 셸 및 포크 생성으로 인해 작업 속도가 크게 느려질 수 있습니다.grepxargsgrep255xargs

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

관련 정보