두 개의 텍스트 파일이 있습니다. 하나는 이름, 이메일 주소 및 기타 필드가 포함된 텍스트 파일입니다. 일부 라인은 다음에서 제공됩니다 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두 번째 필드를 누르세요
sort -k2,2 -t: file1 >file1.sorted
유형파일 2
sort file2 >file2.sorted
이메일 필드를 통해 파일 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
.
자세한 내용은 매뉴얼 페이지를 참조하십시오.