한 파일에서 다른 파일에없는 줄을 찾는 빠른 방법?


241

두 개의 큰 파일 (파일 이름 집합)이 있습니다. 각 파일에 약 30.000 줄이 있습니다. file2에없는 file1에서 줄을 찾는 빠른 방법을 찾으려고합니다.

예를 들어, 이것이 file1 인 경우 :

line1
line2
line3

그리고 이것은 file2입니다.

line1
line4
line5

그런 다음 내 결과 / 출력은 다음과 같아야합니다.

line2
line3

이것은 작동합니다 :

grep -v -f file2 file1

그러나 큰 파일에 사용하면 매우 느립니다.

나는이 사용 DIFF ()를 할 수있는 좋은 방법이 의심되지만 출력은 없어야 단지 다른 라인, 아무것도, 나는 그것을 위해 스위치를 찾을 수 없습니다.

bash 및 기본 Linux 바이너리를 사용 하여이 작업을 수행하는 빠른 방법을 찾도록 도와 줄 수 있습니까?

편집 : 내 자신의 질문에 후속 조치로, diff ()를 사용하여 지금까지 찾은 가장 좋은 방법입니다.

diff file2 file1 | grep '^>' | sed 's/^>\ //'

더 좋은 방법이 있어야합니까?


1
더 빠르면 시도해 볼 수 있습니다.awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
Kent


4
grep -v -f file2 file1에 대해 알려 주셔서 감사합니다
Rahul Prasad


축소 된 도구 세트를 사용한 간단한 방법 : cat file1 file2 file2 | sort | uniq --unique, 아래 답변을 참조하십시오.
Ondra Žižka

답변:


233

GNU diff출력 에서 이전 / 새 / 변경되지 않은 행의 형식을 제어하여이를 달성 할 수 있습니다 .

diff --new-line-format="" --unchanged-line-format=""  file1 file2

이 작업을 수행하려면 입력 파일 을 정렬해야합니다 . bash(및 zsh)을 사용하면 프로세스 대체를 사용하여 적절하게 정렬 할 수 있습니다 <( ).

diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)

위의 줄 과 변경되지 않은 줄은 표시 되지 않으므로 변경된 줄만 출력됩니다 (예 : 제거 된 줄). 당신은 또한 몇 가지 사용할 수 있습니다 diff같은 다른 솔루션이 제공하지 않는 옵션 -i의 경우, 또는 다양한 공백 옵션 (무시 -E, -b, -v덜 엄격한 매칭 등).


설명

옵션은 --new-line-format, --old-line-format그리고 --unchanged-line-format당신이 방법은 제어 할 수 diff비슷한 차이, 포맷 printf형식 지정자를. 이 옵션은 각각 (추가), 이전 (제거) 및 변경되지 않은 행 을 형식화합니다 . 1을 비워 ""로 설정하면 해당 종류의 행이 출력되지 않습니다.

통합 diff 형식에 익숙한 경우 다음을 사용하여 부분적으로 다시 만들 수 있습니다.

diff --old-line-format="-%L" --unchanged-line-format=" %L" \
     --new-line-format="+%L" file1 file2

%L지정 문제의 선이며, 우리는 "+" "각 접두사 -처럼,"또는 "" diff -u (그것은 단지 출력 차이, 그것이 부족합니다 --- +++@@각 그룹화 변화의 상단 선). 당신은 또한 같은 다른 유용한 일을하려면이 옵션을 사용할 수있는 번호를 각 라인%dn.


diff방법은 (다른 제안 comm과 함께 및 join) 정렬 된 입력 으로 예상 출력 만 생성 하지만 <(sort ...)정렬 하는 데 사용할 수 있습니다 . 여기에 간단 awk(nawk) 스크립트 (스크립트에 의해 영감은 연결된에 Konsolebox의 대답) 임의의 입력 파일을 정렬 받아들이, 그리고 그들이 FILE1에서 발생하는 순서에 누락 된 라인을 출력합니다.

# output lines in file1 that are not in file2
BEGIN { FS="" }                         # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; }     # file1, index by lineno
(NR!=FNR) { ss2[$0]++; }                # file2, index by string
END {
    for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}

이것은 file1의 전체 내용을 한 줄씩 색인 배열 ll1[]로 저장하고 file2의 전체 내용을 한 줄씩 색인 연관 배열로 저장 ss2[]합니다. 두 파일을 모두 읽은 후 반복 ll1하여 in연산자를 사용하여 file1의 행이 file2에 있는지 판별하십시오. ( diff중복이 있으면 메소드 와 다른 출력을 갖습니다 .)

파일이 충분히 커서 파일을 모두 저장하는 데 메모리 문제가 발생하는 경우 file1 만 저장하고 file2를 읽는 동안 일치 항목을 삭제하여 CPU를 메모리로 교환 할 수 있습니다.

BEGIN { FS="" }
(NR==FNR) {  # file1, index by lineno and string
  ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) {  # file2
  if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
  for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}

두 어레이의 상기 저장 FILE1의 전체 내용을, 행 번호에 의해 인덱싱 하나 ll1[], 광고 내용에 의해 인덱싱 하나 ss1[]. 그런 다음 file2를 읽으면 일치하는 각 줄이 ll1[]및 에서 삭제됩니다 ss1[]. 마지막에는 원래 순서를 유지하면서 file1의 나머지 행이 출력됩니다.

이 경우 언급 한 문제로 GNU를 사용하여 나누고 정복 할 수 있습니다 split(필터링은 GNU 확장입니다).

split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1

의 사용 및 배치 참고 -의미 stdin상의 gawk명령 줄을. 이것은 split호출 당 20000 라인 청크 단위로 file1에서 제공 됩니다.

비 GNU 시스템에 대한 사용자의 경우, 거의 확실하게 존재는 GNU로 coreutils이의 일환으로 OSX에 포함, 당신이 얻을 수있는 패키지 애플의 Xcode GNU를 제공하는 도구 diff, awk하지만 단지 POSIX / BSD split가 아닌 GNU 버전.


1
이것은 엄청난 grep에 걸리는 시간의 작은 부분에서 내가 필요한 것을 정확하게 수행합니다. 감사!
Niels2000


우리 중 일부는 gnu에 있지 않습니다 [OS X bsd here ...] :)
rogerdpack

1
나는 당신이 의미하는 것으로 가정합니다 diff: 일반적으로 입력 파일은 다를 것이고, diff그 경우 1이 반환됩니다 . 보너스로 생각하십시오 ;-) 쉘 스크립트에서 테스트 중이고 0과 1이 종료 코드 인 것으로 예상되면 2는 문제를 나타냅니다.
mr.spuratic

1
@ mr.spuratic 아 아, 이제는에서 찾을 수 있습니다 man diff. 감사!
Archeosudoerus

246

통신의 (짧은 "일반"에 대한) 명령을 유용 할 수 있습니다comm - compare two sorted files line by line

#find lines only in file1
comm -23 file1 file2 

#find lines only in file2
comm -13 file1 file2 

#find lines common to both files
comm -12 file1 file2 

man파일은 실제로 이것에 대한 아주 읽을 수 있습니다.


6
OSX에서 완벽하게 작동합니다.
pisaruk

41
정렬 된 입력에 대한 요구 사항이 강조 표시되어야합니다.
tripleee

21
comm또한 입력이 정렬되었는지 확인할 --check-order수있는 옵션이 있습니다 (어쨌든 그렇게 보이지만이 옵션은 계속하는 대신 오류가 발생합니다). 그러나 파일을 정렬하려면 간단히 다음 com -23 <(sort file1) <(sort file2)과 같이하십시오.
마이클

Windows에서 생성 된 파일과 Linux에서 생성 된 파일을 비교하고 comm전혀 작동하지 않는 것 같습니다 . 줄 끝에 대해 ​​알아내는 데 시간이 좀 걸렸습니다. 줄 끝이 다른 경우 똑같이 보이는 줄도 다른 것으로 간주됩니다. 이 명령 dos2unix은 CRLF 줄 끝을 LF로만 변환하는 데 사용할 수 있습니다.
ZeroOne

23

Konsolebox가 제안한 것처럼, 포스터 grep 솔루션

grep -v -f file2 file1

단순히 -F옵션을 추가 하면 패턴을 정규 표현식 대신 고정 문자열로 처리하기 위해 실제로 훌륭하게 작동 합니다. 나는 ~ 1000 줄 파일 목록에서 이것을 비교해야한다고 검증했다. 으로 -F는 (실제) 2.278의했다없이하면서 (실제), 0.031의했다,에 그렙 출력을 리디렉션 할 때 wc -l.

이러한 테스트 -x에는 file2가 file1의 하나 이상의 행 중 일부만 일치하는 행을 포함하는 경우 완전히 정확도를 보장하기 위해 솔루션의 일부인 스위치 도 포함 되었습니다.

따라서 입력을 정렬 할 필요가없는 빠르고 유연한 솔루션 (대소 문자 구분 등)은 다음과 같습니다.

grep -F -x -v -f file2 file1

모든 버전의 grep에서 작동하지는 않습니다. 예를 들어 macOS에서는 실패합니다. 파일 1의 행이 파일의 하위 문자열 인 다른 행과 일치하더라도 파일 2에 존재하지 않는 것으로 표시됩니다. . 또는 이 솔루션을 사용하기 위해 macOSGNU grep을 설치할 수 있습니다 .


그래, 그것은 작동하지만 -F이것으로도 잘 확장되지는 않습니다.
몰 롬비

이것은 그렇게 빠르지 않습니다. 저는 포기하기 전에 ~ 500k 줄의 2 개 파일을 5 분 동안 기다렸습니다
cahen

이 하나가 정렬되지 않은 파일을 따라서 unsorting에 의해 아래로 드래그 처리 할 수 있기 때문에 실제로이 방법은, 통신 방식에 비해 여전히 느린, 통신 정렬의 장점합니다
workplaylifecycle

@workplaylifecycle 매우 큰 병목 현상 일 수있는 정렬 시간을 추가해야합니다 file2.
rwst

그러나 -x옵션 이있는 grep 은 더 많은 메모리를 사용합니다. file26-10 바이트의 180M 단어를 포함 하여 내 프로세스는 Killed32GB RAM 시스템에 도착 했습니다.
rwst

11

정렬 및 diff의 속도는 얼마입니까?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted

1
diff를 수행하기 전에 파일을 정렬해야한다는 사실을 상기시켜 주셔서 감사합니다. sort + diff가 훨씬 빠릅니다.
Niels2000

4
하나의 라이너 ;-) diff <(정렬 파일 1 -u) <(정렬 파일 2-u)
steveinatorx

11

당신이 최소한의 리눅스 배포판의 예 : "멋진 도구"짧은 경우,이 단지와 솔루션입니다 cat, sortuniq:

cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

테스트:

seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

# Output:
1
2    

이것 에 비해 비교적 빠릅니다 grep.


1
참고-일부 구현에서는 --unique옵션을 인식하지 못합니다 . 이를 위해 표준화 된 POSIX 옵션 을 사용할 수 있어야합니다 .| uniq -u
AndrewF

1
이 예에서 "2"는 어디에서 왔습니까?
Niels2000

1
@ Niels2000 seq 1 1 7은 1부터 7까지 1 씩 증가합니다. 즉 1 2 3 4 5 6 7입니다. 그리고 바로 2가 있습니다!
Eirik Lygre

5
$ join -v 1 -t '' file1 file2
line2
line3

-t당신이 라인의 일부에 공백이 있다면 그것은 전체 라인을 비교 있는지 확인합니다.


처럼 comm, join조인 작업을 수행중인 필드에서 두 입력 행을 정렬해야합니다.
tripleee

4

파이썬을 사용할 수 있습니다 :

python -c '
lines_to_remove = set()
with open("file2", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("f1", "r") as f:
    for line in f.readlines():
        if line.strip() not in lines_to_remove:
            print(line.strip())
'

4

사용 combine에서 moreutils, 패키지 세트 유틸리티를 그 지원 not, and, or, xor작업

combine file1 not file2

즉, file1에 있지만 file2에는없는 줄을 제공하십시오.

또는 file1의 줄에서 file2의 줄을 뺍니다.

참고 : combine 작업을 수행하기 전에 두 파일에서 고유 한 줄을 정렬하고 찾습니다 diff. 따라서 diff와의 출력간에 차이가있을 수 있습니다 combine.

실제로 당신은 말하고 있습니다

file1과 file2에서 다른 줄을 찾은 다음 file1의 줄에서 file2의 줄을 뺍니다.

내 경험상 다른 옵션보다 훨씬 빠릅니다.


2

grep에 fgrep을 사용하거나 -F 옵션을 추가하면 도움이 될 수 있습니다. 그러나 더 빠른 계산을 위해서는 Awk를 사용할 수 있습니다.

다음 Awk 방법 중 하나를 시도 할 수 있습니다.

http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219


2
+1 입력을 정렬 할 필요가없는 유일한 답변입니다. 분명히 OP는이 요구 사항에 만족했지만 많은 실제 시나리오에서 용납 할 수없는 제약입니다.
tripleee

1

내가 일반적으로하는 방법은 --suppress-common-lines플래그를 사용하는 것이지만, 나란히 형식으로하는 경우에만 작동합니다.

diff -y --suppress-common-lines file1.txt file2.txt


0

나는 정상적인 if 및 for 루프 문을 사용하면 완벽하게 작동한다는 것을 알았습니다.

for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done

2
DontReadLinesWithFor를 참조하십시오 . 또한이 코드는 grep결과가 여러 단어로 확장되거나 file2쉘에서 글로브로 항목을 처리 할 수있는 경우 매우 잘못 작동합니다 .
찰스 더피
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.