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


16

파일의 어느 곳에서나 내가 찾고있는 전체 키워드 세트가 포함 된 디렉토리의 모든 파일을 나열하는 방법을 찾고 있습니다.

따라서 키워드가 같은 줄에 표시 될 필요는 없습니다.

이를 수행하는 한 가지 방법은 다음과 같습니다.

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}' *동일한 효과를 얻는 것이 더 좋을 것이므로 이것을 허용하는 솔루션을 선호합니다.


1
gzip친숙 할 필요는 없습니다 zcat. 파일 만 있으면 됩니다.
terdon

@terdon 게시물을 편집하여 파일이 압축되었다고 언급 한 이유를 설명했습니다.
arekolek 2016 년

awk를 한 번 또는 여러 번 실행하는 것에는 큰 차이가 없습니다. 내 말은, 약간의 오버 헤드가 있지만 차이점을 알지 못할 것입니다. 물론 스크립트 자체가 무엇이든 awk / perl을 만들 수는 있지만 빠른 원 라이너가 아닌 완전한 프로그램이되기 시작합니다. 너가 원하는게 그거야?
terdon

@ terdon 개인적으로, 나에게 더 중요한 측면은 명령이 얼마나 복잡 할 것인가입니다 (댓글을 작성하는 동안 두 번째 편집이 온 것 같습니다). 예를 들어, 호출 grep앞에 접두사를 붙이면 솔루션을 쉽게 조정할 수 있으며 파일 이름도 처리 할 필요가 없습니다. grepz
arekolek

예, 그러나 그것은 grep입니다. AFAIK 만 grep하고 cat"z는-변형"표준 있습니다. 나는 당신이 for f in *; do zcat -f $f ...솔루션을 사용하는 것보다 더 간단한 것을 얻을 것이라고 생각하지 않습니다 . 다른 것은 파일을 열기 전에 파일 형식을 확인하거나 라이브러리를 사용하여 동일한 작업을 수행하는 완전한 프로그램이어야합니다.
terdon

답변:


13
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)을 압축 해제하십시오. 또는 파이썬에서는 압축 파일을 처리하기위한 모듈도 있습니다.


다음은 여러 패턴과 파일 이름 (일반 텍스트 또는 압축 텍스트 포함)을 허용 perl하는 데 사용 되는 버전입니다 IO::Uncompress::AnyUncompress.

이전의 모든 인수 --는 검색 패턴으로 취급됩니다. 이후의 모든 인수 --는 파일 이름으로 취급됩니다. 이 작업에 대한 기본적이지만 효과적인 옵션 처리. 또는 모듈을 -i사용하면 더 나은 옵션 처리 (예 : 대소 문자를 구분하지 않는 검색을위한 옵션 지원 )를 수행 할 수 있습니다 .Getopt::StdGetopt::Long

다음과 같이 실행하십시오.

$ ./arekolek.pl one two three -- *.gz *.txt
1.txt.gz
4.txt.gz
5.txt.gz
1.txt
4.txt
5.txt

(I는 목록 파일하지 않습니다 {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수정자를 사용하여 $pattern실행 중에 변경되지 않는다는 것을 한 번만 컴파일 map()) 각 파일의 일치 항목을 포함하는 해시 (% s)를 작성하는 데 사용됩니다.

현재 파일에 모든 패턴이 표시 될 때마다 $m_string((정렬 키가 if %s와 같은지 비교 $p_string)) 파일 이름을 인쇄하고 다음 파일로 건너 뜁니다.

이것은 특히 빠른 해결책은 아니지만 부당하게 느리지 않습니다. 첫 번째 버전은 74MB 상당의 압축 로그 파일에서 총 3 개의 단어를 검색하는 데 4m58s가 소요되었습니다 (총 937MB의 비 압축). 이 현재 버전은 1m13s가 걸립니다. 추가 최적화가 이루어질 수 있습니다.

한 가지 분명한 최적화와 함께이를 사용하는 것입니다 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에서 언급 한 바와 같이, 여러 zgrep과의 find -exec또는 xargs매우 빠르게 할 수 있지만,이 스크립트를 검색 할 패턴의 수를 지원하는 장점을 가지고 있으며, 압축의 여러 가지 유형을 처리 할 수 있습니다.

스크립트가 각 파일의 처음 100 줄만 검사하도록 제한되어 있으면 0.6 초 안에 모든 파일 (74MB의 269 파일 샘플)을 실행합니다. 이것이 어떤 경우에 유용한 경우, 명령 행 옵션 (예 :)으로 만들 수 -l 100있지만 일치하는 파일 을 모두 찾지 못할 수도 있습니다.


BTW에 대한 매뉴얼 페이지에 따르면 IO::Uncompress::AnyUncompress지원되는 압축 형식은 다음과 같습니다.


마지막으로 (나는 희망) 최적화. 대신 PerlIO::gzip데비안 패키지로 모듈 을 사용하면 74MB의 로그 파일을 처리하는 데 약 3.1 초가 소요 됩니다. 간단한 해시를 사용하는 것보다는 약간의 개선이있었습니다 ( 버전 으로 몇 초도 절약되었습니다 ).libperlio-gzip-perlIO::Uncompress::AnyUncompressSet::ScalarIO::Uncompress::AnyUncompress

PerlIO::gzip/programming//a/1539271/137158 에서 가장 빠른 perl gunzip으로 권장되었습니다 (Google 검색에서 찾음 perl fast gzip decompress)

xargs -P이것과 함께 사용하면 전혀 향상되지 않았습니다. 실제로 0.1 초에서 0.7 초까지 속도가 느려지는 것처럼 보였습니다. (나는 네 번의 달리기를 시도했고 내 시스템은 백그라운드에서 다른 것들을 수행하여 타이밍을 바꿀 것입니다)

가격은이 버전의 스크립트는 압축 및 압축되지 않은 파일 만 처리 할 수 ​​있다는 것입니다. 속도 대 유연성 :이 버전의 경우 3.1 초 IO::Uncompress::AnyUncompress, xargs -P래퍼가 있는 버전의 경우 23 초 (또는 1m13s없는 경우 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;
    }
  }
}

for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; done잘 작동하지만 실제로 내 grep솔루션보다 3 배나 오래 걸리며 실제로는 더 복잡합니다.
arekolek

1
OTOH, 일반 텍스트 파일의 경우 더 빠릅니다. 펄이나 파이썬과 같은 압축 파일을 읽는 기능을 지원하는 언어로 구현 된 동일한 알고리즘은 여러 greps보다 빠릅니다. "복잡도"는 부분적으로 주관적입니다-개인적으로, 나는 하나의 awk 또는 perl 또는 python 스크립트가 찾기가 있거나없는 여러 greps보다 덜 복잡하다고 생각합니다 ... 압축 된 모든 파일에 대해 zcat을 forking 비용으로)
cas

나는했다 apt-get install libset-scalar-perl스크립트를 사용 할 수 있습니다. 그러나 합리적인 시간에 종료되지 않는 것 같습니다.
arekolek

검색중인 파일의 크기와 크기 (압축 및 비 압축)는 얼마입니까? 수십 또는 수백 개의 중소형 파일 또는 수천 개의 큰 파일?
cas

다음 은 압축 파일 크기히스토그램입니다 (20 ~ 100 개 파일, 최대 50MB이지만 대부분 5MB 미만). 압축되지 않은 모양은 동일하지만 크기에 10을 곱한 값입니다.
arekolek

11

전체 파일을 한 줄로 처리 .하도록 레코드 구분 기호를 설정하십시오 awk.

awk -v RS='.' '/one/&&/two/&&/three/{print FILENAME}' *

마찬가지로 perl:

perl -ln00e '/one/&&/two/&&/three/ && print $ARGV' *

3
산뜻한. 그래도 전체 파일을 메모리에로드하므로 큰 파일에는 문제가 될 수 있습니다.
terdon

유망 해 보였기 때문에 처음에 이것을 상향 조정했습니다. 그러나 gzip으로 압축 된 파일로 작업 할 수는 없습니다. for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; done아무것도 출력하지 않습니다.
arekolek

@arekolek 저 루프는 저에게 효과적입니다. 파일이 제대로 압축되어 있습니까?
jimmij 2016 년

@arekolek zcat -f "$f"파일 중 일부가 압축되지 않은 경우 필요 합니다.
terdon

압축되지 않은 파일에서도 테스트했지만 awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txt결과는 반환되지 않지만 grep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))예상 결과는 반환됩니다.
arekolek

3

압축 파일의 경우 각 파일을 반복하고 먼저 압축을 풀 수 있습니다. 그런 다음 다른 답변의 약간 수정 된 버전으로 다음을 수행 할 수 있습니다.

for f in *; do 
    zcat -f "$f" | perl -ln00e '/one/&&/two/&&/three/ && exit(0); }{ exit(1)' && 
        printf '%s\n' "$f"
done

펄 스크립트는 0세 개의 문자열이 모두 발견되면 상태 (성공)로 됩니다. 의 }{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 *

2

지금까지 제안 된 모든 솔루션 중에서 grep을 사용하는 원래의 솔루션이 가장 빠르며 25 초 안에 완료됩니다. 단점은 키워드를 추가하고 제거하는 것이 지루하다는 것입니다. 그래서 multi동작을 시뮬레이트하지만 구문을 변경할 수 있는 스크립트 (dubbed )를 생각해 냈습니다 .

#!/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 스크립트를 실험했습니다. 모든 키워드를 한 줄씩 검색하고 전체 파일을 키워드로 키워드별로 검색합니다. 내 경우에는 두 번째 전략이 더 빨랐습니다. 그러나 grep33 초 안에 마무리하는 것보다 속도가 느 렸습니다 . 한 줄씩 키워드 검색이 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 초의 월 타임이 걸렸습니다. 내 파이썬 스크립트가 49 초의 벽 시간 ( grep29 초)을 소비했기 때문에 흥미 롭습니다 .

cas스크립트grep 는 4 초 미만 으로 처리 된 적은 수의 파일에서도 적절한 시간 내에 종료되지 않아서 종료해야했습니다.

그러나 그의 원래 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제안 사이의 실행 시간에는 큰 차이가 없으며 실행 하는 데 1 초가 걸립니다.

FNR == 1 { f1=f2=f3=0; }이러한 경우 변수 초기화 는 모든 후속 처리 파일에 대한 카운터를 재설정하기 위해 필수적입니다. 따라서이 솔루션을 사용하려면 키워드를 변경하거나 새 키워드를 추가하려면 세 곳에서 명령을 편집해야합니다. 반면에, grep당신은 추가 할 수 있습니다| xargs grep -l four 원하는 키워드를 하거나 수정하면됩니다.

grep명령 대체를 사용 하는 솔루션 의 단점은 체인의 어느 위치에서나 마지막 단계 이전에 일치하는 파일이 없으면 중단되는 것입니다. xargs파이프가 중단되면 grep0이 아닌 상태를 반환 하므로 변형에 영향을 미치지 않습니다 . 스크립트를 사용 xargs하도록 업데이트 했으므로 직접 처리 할 필요가 없으므로 스크립트가 더 간단 해집니다.


파이썬 솔루션은 다음을 사용하여 루프를 C 레이어로 푸시하는 것이 not all(p in text for p in patterns)
좋습니다.

@iruvar 제안 해 주셔서 감사합니다. 나는 그것을 시도하고 (sans not) 32 초 안에 완료되었으므로 그다지 개선되지는 않았지만 확실히 더 읽기 쉽습니다.
arekolek

awk에서 f1, f2, f3 대신 연관 배열을 사용할 수 있습니다. key = search-pattern, val = count
cas

@arekolek PerlIO::gzip대신 내 최신 버전을 사용하십시오 IO::Uncompress::AnyUncompress. 이제 74MB의 로그 파일을 처리하는 데 1m13 대신 3.1 초만 걸립니다.
cas

이전에 실행 한 경우 BTW, eval $(lesspipe)(예에 .profile, 등), 당신이 사용할 수있는 less대신에 zcat -f당신의 for주위에 래퍼 루프는 awk그 모든 종류의 파일을 처리 할 수있을 것입니다 less.... 캔 (gzip을, 레스 햇의 bzip2, XZ 등) less는 stdout이 파이프인지 감지 할 수 있으며 stdout에 스트림을 출력합니다.
cas

0

또 다른 옵션 - 공급 단어 하나 한 번에 xargs그것을 실행하려면 grep파일에 대해. 반품에 의한 반품 실패로 돌아 오면 xargs즉시 종료 할 수 있습니다 ( 문서 확인 ). 물론이 솔루션에 관련된 포탄 및 포크 생성은 상당히 느려질 것입니다.grep255xargs

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

이것은 멋지지만 이것을 사용하는 방법을 잘 모르겠습니다. 무엇 _file? 인수로 전달 된 여러 파일에서이 검색을 수행하고 모든 키워드가 포함 된 파일을 반환합니까?
arekolek

@arekolek는 루프 버전을 추가했습니다. 그리고 _, 그것은 $0생성 된 쉘 에 전달됩니다 -이것은 출력에 명령 이름으로 표시됩니다 ps-나는 여기 마스터 에게 연기합니다
iruvar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.