한 파일(3.2Gb)의 패턴이 다른 파일(4.8Gb)과 일치하는 grep

한 파일(3.2Gb)의 패턴이 다른 파일(4.8Gb)과 일치하는 grep

두 개의 텍스트 파일이 있습니다. 하나는 이름, 이메일 주소 및 기타 필드가 포함된 텍스트 파일입니다. 일부 라인은 다음에서 제공됩니다 file1.

John:[email protected]:johnson123:22hey
Erik:[email protected]:johnson133:22hey
Robert:[email protected]:johnson123:21hey
Johnnny:[email protected]:johnson123:22hey

다른 하나에는 이메일 주소만 포함되어 있습니다. 다음의 예 file2:

[email protected]
[email protected]
[email protected]
[email protected]

file1나는 모든 행에 이메일 주소가 있는 출력을 원합니다 file2. 예를 들어 [email protected]is in file2이므로 여기에서 다음 줄을 보고 싶습니다 file1.

John:[email protected]:johnson123:22hey

file1"이메일 주소 목록"과 일치하는 행을 검색하고 출력하는 쉬운 방법이 있습니까 file2?

나는 몇 시간 동안 검색해 왔지만 지금까지 Google 검색(및 StackOverflow 검색)과 명령줄 노력은 성과가 없었습니다.

내가 시도해 본 결과 작동하는 명령은 다음과 같습니다.

fgrep -f file2.txt file1.txt > matched.txt
grep -F -f ....
grep -F -x -f file1 file2 > common 

잠깐만요, 그런데 둘 다 알아냈어요 grep memory exhausted. 제가 일치하는 파일은 4.8GB( file1)와 3.2GB( file2이메일 주소만 포함)였습니다. 내 생각엔 이 명령이 메모리를 소모할 것 같아요. 명령을 보다 원활하게 실행하는 방법을 찾았 find지만 작동하지 않았습니다.

개요;일치 항목이 필요합니다 file2. file1행 중 하나가 file2의 행과 일치 하면 file1출력합니다. 파일이 크기 때문에 모든 메모리를 사용하지 않는 안전한 방법이 필요합니다.

감사합니다. 하루 종일 이것을 찾고 실험했으며 포기하고 싶지 않았습니다(5시간 이상).

답변1

대용량 파일을 조작하는 것은 매우 어렵지만 다음 세 단계로 처리할 수 있습니다.

  1. 유형파일 1두 번째 필드를 누르세요

    sort -k2,2 -t: file1 >file1.sorted
    
  2. 유형파일 2

    sort file2 >file2.sorted
    
  3. 이메일 필드를 통해 파일 2개 결합

    join -t: -2 2 file2.sorted file1.sorted -o 2.1,0,2.3,2.4 >matched.txt
    

답변2

나는 이 질문에 대한 두 번째 답변을 제출하려고 합니다(흥미로운 질문입니다). 이것은 내 SQLite 솔루션과 완전히 다르며 이제 나타나기 시작하는 유망해 보이는 솔루션과도 완전히 다릅니다 sort.join

원래 접근 방식을 사용 grep -f하지만 실제로는 문제를 약간 줄입니다. .txt를 사용하여 "쿼리 파일"을 file2관리 가능한 덩어리로 분할해 보겠습니다 split.

split유틸리티는 줄 수에 따라 파일을 여러 개의 작은 파일로 분할할 수 있습니다.

다음을 포함하는 3.2GB 파일평균 줄 길이는 20자입니다.약 172,000,000개의 행이 있습니다(수학적 실수를 하지 않는 한). 2000개의 파일로 나누어 파일당 85000라인이 가능합니다.

그래서,

$ mkdir testing
$ cd testing
$ split -l 85000 -a 4 ../file2

-a 4옵션은 split첫 번째 문자 다음에 4개의 문자를 사용하여 x새 파일의 이름을 지정하도록 지시합니다. 이러한 파일은 등 으로 호출됩니다 xaaaa.xaaab

그런 다음 다음에서 원래 버전을 실행하십시오 grep -f.

for f in x????; do
  grep -F -f "$f" ../file1
done

이것가능한grep이제 메모리에 훨씬 더 작은 쿼리 패턴 집합을 유지할 수 있습니다 .

고쳐 쓰다split -l 72000 -a 4: 약 2000개의 파일을 생성하는 데 145,526,885개의 라인이 사용됩니다 .

testing새로운 분할 파일 세트를 생성하려고 할 때마다 이 디렉터리를 지워야 합니다.

이 답변의 분할 파일은 이 질문에 대해 얻을 수 있는 다른 답변에 대한 입력으로 단독으로 사용될 수 있습니다.

답변3

귀하의 구체적인 질문을 고려할 때 100% 일치하는 필드가 있으므로 Costa의 답변이 가장 적합할 것입니다.

하지만 귀하의 질문이 실제로 있다면예전에는수십억 줄에 걸쳐 수백만 개의 정규식을 파악하고 있으며 GNU Parallel은 이를 수행하는 방법을 설명합니다.https://www.gnu.org/software/parallel/man.html#예: -Grepping-n-lines-for-m-regular-expressions

정규식이 많은 대용량 파일을 grep하는 가장 간단한 솔루션은 다음과 같습니다.

grep -f regexps.txt bigfile

또는 정규식이 고정 문자열인 경우:

grep -F -f regexps.txt bigfile

CPU, RAM, 디스크 I/O라는 3가지 제한 요소가 있습니다.

RAM은 측정하기 쉽습니다. grep 프로세스가 사용 가능한 메모리의 대부분을 차지하는 경우(예: top 실행 시) RAM은 제한 요소입니다.

CPU는 측정하기도 쉽습니다. grep이 CPU의 90%를 초과하는 경우 CPU가 제한 요소이므로 병렬화가 속도를 높입니다.

디스크 I/O가 제한 요소인지 확인하기 어렵고 디스크 시스템에 따라 병렬화가 더 빨라질 수도 있고 느려질 수도 있습니다. 확실히 알 수 있는 유일한 방법은 테스트하고 측정하는 것입니다.

제한 요소: 메모리

큰 파일에 대한 일반 grep -f regexs.txt는 크기에 관계없이 작동하지만 regexps.txt가 너무 커서 메모리에 맞지 않으면 분할해야 합니다.

grep -F에는 약 100바이트의 RAM이 필요한 반면, grep에는 정규 표현식 1바이트당 약 500바이트의 RAM이 필요합니다. 따라서 regexps.txt가 RAM의 1%를 차지한다면 아마도 너무 큰 것입니다.

정규식을 고정 문자열로 변환할 수 있다면 그렇게 하십시오. 예를 들어, 대용량 파일에서 모두 다음과 같은 줄을 찾는 경우:

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

그런 다음 regexps.txt를 다음에서 변환할 수 있습니다.

ID1.*Identifier1
ID2.*Identifier2

입력하다:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

이렇게 하면 약 80% 더 적은 메모리를 사용하고 더 빠른 grep -F를 사용할 수 있습니다.

그래도 메모리에 맞지 않으면 다음을 수행할 수 있습니다.

parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

1M은 사용 가능한 메모리를 코어 수로 나눈 값입니다. grep -F의 경우 200, 일반 grep의 경우 1000입니다. GNU/Linux에서는 다음과 같이 할 수 있습니다:

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

중복된 줄과 잘못된 순서를 허용할 수 있다면 다음을 수행하는 것이 더 빠릅니다.

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile

제한 요소: CPU

CPU가 제한 요소인 경우 정규식을 병렬화해야 합니다.

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

이 명령은 CPU당 grep을 시작하고 CPU당 한 번 큰 파일을 읽습니다. 그러나 이 작업은 병렬로 수행되므로 첫 번째 읽기를 제외한 모든 읽기가 RAM에 캐시됩니다. regexp.txt의 크기에 따라 -L1000 대신 --block 10m을 사용하는 것이 더 빠를 수도 있습니다.

일부 스토리지 시스템은 여러 블록을 병렬로 읽을 때 성능이 더 좋습니다. 이는 일부 RAID 시스템 및 일부 네트워크 파일 시스템에 해당됩니다. 대용량 파일을 병렬로 읽기:

parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt

그러면 빅파일이 100MB 청크로 분할되고 각 청크에 대해 grep이 실행됩니다. bigfile과 regexp.txt를 병렬로 읽으려면 --fifo를 사용하여 두 가지를 결합하십시오.

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}

두 개 이상의 정규 표현식과 일치하면 한 줄이 중복될 수 있습니다.

더 큰 문제

문제가 너무 커서 해결할 수 없다면 아마도 Lucene을 사용할 준비가 된 것입니다.

답변4

join데이터베이스 솔루션 사용을 피해야 하는 경우(이유는 모르겠지만 나에게는 가장 좋은 생각인 것 같습니다) 이메일 주소에서 두 파일을 모두 정렬한 다음 데이터베이스의 기능을 대략적으로 보여주는 이 명령을 사용하여 수행할 수 있습니다 .

이것이 내가 한 일입니다:

sort -t: +1 file1 -o file1
sort file2 -o file2
join -t: -o 1.1,1.2,1.3,1.4 -1 2 file1 file2

이는 샘플 데이터에 적합한 것으로 보입니다. 파일을 정렬합니다제자리에. 이 작업을 수행하지 않으려면 s -o의 옵션을 sort임시 파일 이름으로 변경하고 이를 조인에 사용하세요. 또한 첫 번째 파일에 실제로 4개 이상의 필드가 있는 경우 -o옵션 에서 이를 고려해야 합니다 join.

자세한 내용은 매뉴얼 페이지를 참조하십시오.

관련 정보