grep 파일의 20,000단어

grep 파일의 20,000단어

내 폴더에는 5000개의 파일이 있습니다. 파일 이름은 XX0000001~XX0005000입니다.

각 파일에서 단어를 가져온 다음 그 단어와 다른 파일(target.txt)의 다음 줄을 grep하려고 합니다.

내 XX* 파일 중 일부에는 약 30,000개의 단어가 포함되어 있습니다.

이를 수행할 수 있는 방법이 있습니까?

나는 시도했다:

start_number=0000001
end_number=0005000
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for ((i=start_number; i <=end_number; i++)); do
   filename="XX$(printf "%07d" "$i")"
   output_file="$output_folder/output_${filename}.txt"
   while read -r word; do
      awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
   done < "$words_file"
done

이 작업을 수행하는 더 빠른 방법이 있나요? 내 대상 파일에는 검색할 수백만 줄이 있습니다. 정확한 대상 파일 크기는 20GB, 106441678줄입니다.

예: XX0000001 파일은 다음과 같습니다.

Big1 Big5 Big7 Big10 Big11

(그리고 단어가 너무 많습니다. 일부 XX 파일에는 최대 30,000개의 단어가 포함될 수도 있습니다.)

Target.txt 파일은 다음과 같습니다.

#Big1

This_is_a_file_containing_xxxxx

#Big2

This_is_a_file_containing_xxxxx

#Big3

This_is_a_file_containing_xxxxx

#Big4

This_is_a_file_containing_xxxxx

#Big5

This_is_a_file_containing_xxxxx

#Big6

This_is_a_file_containing_xxxxx

#Big7

This_is_a_file_containing_xxxxx

#Big8

This_is_a_file_containing_xxxxx

#Big9

This_is_a_file_containing_xxxxx

#Big10

This_is_a_file_containing_xxxxx

#Big11

This_is_a_file_containing_xxxxx

#Big12

This_is_a_file_containing_xxxxx

답변1

내 폴더에는 5000개의 파일이 있습니다. 파일 이름은 XX0000001~XX0005000입니다.

파일 세트를 반복하려면 for f in XX0*여기와 같은 쉘 glob을 사용하십시오. 일련의 숫자를 반복해야 하는 경우 Bash에서는 숫자를 8진수로 표시하므로 앞에 오는 0에 주의해야 합니다. 예를 들어 루프를 시도해 보고 숫자를 인쇄하고 마지막 숫자를 살펴보세요.

start_number=0000001
end_number=0005000
for ((i=start_number; i <=end_number; i++)); do
   echo $i
done |tail -1

출력은 yes 2560, no 입니다 5000. 하지만 zsh에서는 그렇지 않으며 어떤 셸을 실행하고 있는지 언급하지 않았지만 문제는 지적할 가치가 있을 것입니다.


여기,

   while read -r word; do
      awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
   done < "$words_file"

이것이 무엇을 하는지는 잘 모르겠지만 AWK 스크립트에서는 입력 행의 모든 ​​필드를 반복하지만 $1루프 내에서는 필드 1()만 참조한다는 것을 알았습니다.


이제 파일이 다음과 같다고 가정합니다.

% cat XX0000001
Big1 Big7
% cat XX0000002
Big5 Big10
% cat target.txt
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
[...]

Big1즉 , 파일의 한 줄에 여러 가지 다른 패턴(예: 및 )이 있습니다 (예: 한 줄에 하나씩). 또한 어떤 패턴과도 일치하는 줄을 찾아 다음 줄과 함께 인쇄하고 싶을 것 같습니다.Big7XX0*target.txt

이제 표준 grep은 일치 후 "한 줄 더"를 인쇄할 수 있으며 동시에 여러 패턴을 찾을 수 있습니다. 이 -f옵션은 라인이 패턴을 형성하는 파일의 이름을 사용하므로 XX0*각 패턴이 단일 라인으로 나타나도록 파일을 전처리해야 합니다. 모든 공백을 개행 문자로 변경하면 됩니다 tr. 가장 쉬운 방법은 프로세스 대체를 사용하여 trto의 출력을 grep파일로 사용하는 것이지만 임시 파일을 사용할 수도 있습니다(또는 출력을 to로 파이프할 수도 있음 tr) grep -f -.

예를 들어:

% grep -A1 -f <(tr ' ' '\n' < XX0000001 ) target.txt
#Big1
This_is_a_file_containing_xxxxx
--
#Big7
This_is_a_file_containing_xxxxx
--
#Big10
This_is_a_file_containing_xxxxx

물론 Big1해당 라인에서도 패턴이 발생하므로 #Big10일치합니다. (그러나 grep 옵션을 사용하여 전체 단어 일치를 요청할 수 있습니다 -w.) 구분 기호를 제거하려면 --결과를 파이프할 수 있습니다 grep -ve --.

이것이 얼마나 효율적인지는 grep 구현에 따라 달라질 수 있지만 이러한 목적으로 설계된 도구로서 쉘 스크립트에서 동일한 작업을 수행하는 것보다 최적화 가능성이 더 높습니다. 쉘 스크립트는 느립니다. 모든 패턴이 형식이라면 Big*공통 부분을 한 번만 찾는 것이 현명합니다. 패턴 목록을 단일 패턴으로 변경할 수도 있습니다. Big(1|5|7|10)정규식 엔진에 더 잘 작동할 수 있기를 바랍니다.

답변2

많은 수의 검색어에 대해 매우 큰 파일을 검색하고 있으며 셸/표준 도구에서 "빠른" 솔루션을 찾을 가능성이 거의 없습니다. 즉, 귀하의 접근 방식이 특히 비효율적이라고 생각합니다.

아마도 다음과 같을 것입니다(테스트되지 않음):

words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for filename in XX* ; do 
  output_file="$output_folder/output_${filename}.txt"
  grep -f "$filename" -Fx -A1 "$words_file" > "$output_file"
done

하지만 솔직히 그 속도도 빠르지는 않을 것 같아요.

이는 다중 스레드일 수 있으며, 이는 처리량을 향상시킬 수 있습니다.

이는 다중 스레드 대안입니다. threads이 값을 운영 환경에 맞게 조정 해야 합니다 .

threads=4
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"

find . -maxdepth 1 -type f -name 'XX*' -print0 | \
  xargs -I% -0 -P$threads bash -c '
    file="%"
    output_file="$output_folder/output_${file#./}.txt"
    grep -f "%" -Fx -A1 "$words_file" > "$output_file"
  '

이를 통해 얻을 수 있는 이점은 사용 가능한 메모리, CPU 코어 수, 저장소 속도 및 서버의 기타 활동과 같은 하드웨어 요소에 따라 달라집니다.

답변3

귀하의 질문이 불분명하고 예상되는 출력을 제공하지 않아 필요한 것이 무엇인지 명확히 하는 데 도움이 되지 않으며 잠재적인 솔루션이 작동하는지 테스트할 방법이 없지만 awk를 사용하여 아마도 원하는 것일 수 있습니다. :

awk '
    FILENAME != ARGV[ARGC-1] {
        for ( i=1; i<=NF; i++ ) {
            words[$i]
        }
        next
    }
    f {
        print
    }
    {
        f = 0
        for ( word in words ) {
            if ( $0 ~ word ) {
                print
                f = 1
                next
            }
        }
    }
' some_folder/XX* target.txt

XX*파일이 너무 많으면 다음 ARG_MAX과 같이 변경하세요.

printf '%s\n' some_folder/XX* |
awk '
    FILENAME == "-" {
        ARGV[ARGC++] = $0
        next
    }
    FILENAME != ARGV[ARGC-1] {
        for ( i=1; i<=NF; i++ ) {
            words[$i]
        }
        next
    }
    f {
        print
    }
    {
        f = 0
        for ( word in words ) {
            if ( $0 ~ word ) {
                print
                f = 1
                next
            }
        }
    }
' - target.txt

관련 정보