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

두 개의 큰 파일 (파일 이름 집합)이 있습니다. 각 파일에 약 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/^>\ //'

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



답변

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 버전.


답변

통신의 (짧은 “일반”에 대한) 명령을 유용 할 수 있습니다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파일은 실제로 이것에 대한 아주 읽을 수 있습니다.


답변

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을 설치할 수 있습니다 .


답변

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

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


답변

당신이 최소한의 리눅스 배포판의 예 : “멋진 도구”짧은 경우,이 단지와 솔루션입니다 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.


답변

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

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


답변

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

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())
'