비 Linux Unices(심지어더문서)

비 Linux Unices(심지어더문서)

약 30,000개의 파일이 있습니다. 각 파일에는 약 100,000개의 라인이 포함되어 있습니다. 줄에는 공백이 포함되지 않습니다. 단일 파일 내의 행은 정렬되며 중복되지 않습니다.

나의 목표: 모든 것을 찾고 싶다모두두 개 이상의 파일에 중복된 줄이 있고 중복된 항목이 포함된 파일 이름입니다.

간단한 해결책은 다음과 같습니다.

cat *.words | sort | uniq -c | grep -v -F '1 '

그런 다음 다음을 실행합니다.

grep 'duplicated entry' *.words

더 효율적인 접근 방식이 보이시나요?

답변1

모든 입력 파일이 이미 정렬되어 있으므로 실제 정렬 단계를 건너뛰고 다음을 sort -m사용할 수 있습니다.병합파일을 함께 넣으세요.

일부 Unix 시스템에서는(내가 아는 한오직Linux), 이 정도면 충분할 것 같습니다

sort -m *.words | uniq -d >dupes.txt

file 에 중복된 줄을 씁니다 dupes.txt.

이 줄이 어떤 파일에서 왔는지 찾으려면 다음을 수행하십시오.

grep -Fx -f dupes.txt *.words

grep이는 ( ) 안의 행이 다음과 같이 처리되도록 지시합니다 .dupes.txt-f dupes.txt고정 문자열 패턴( -F). grep또한 전체 라인이 처음부터 끝까지 완벽하게 일치해야 합니다( -x). 파일 이름과 줄을 터미널에 인쇄합니다.

비 Linux Unices(심지어문서)

일부 Unix 시스템에서는 30000개의 파일 이름이 단일 유틸리티에 전달되기에는 너무 긴 문자열로 확장됩니다(이는 내 OpenBSD 시스템에서 수행하는 sort -m *.words인쇄 출력으로 인해 실패함을 의미합니다). Argument list too long파일 수가 훨씬 더 많으면 Linux에서도 이에 대해 불평할 것입니다.

사기꾼을 찾고 있습니다

이는 일반적인 경우(이는 다음에도 적용됨)를 의미합니다.많은(30000개 이상의 파일) 정렬은 "청크"되어야 합니다.

rm -f tmpfile
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
    if [ -f tmpfile ]; then
        sort -o tmpfile -m tmpfile "$@"
    else
        sort -o tmpfile -m "$@"
    fi' sh 

또는 tmpfile다음 없이 생성합니다 xargs.

rm -f tmpfile
find . -type f -name '*.words' -exec sh -c '
    if [ -f tmpfile ]; then
        sort -o tmpfile -m tmpfile "$@"
    else
        sort -o tmpfile -m "$@"
    fi' sh {} +

현재 디렉토리(또는 그 이하)에서 이름이 일치하는 모든 파일을 찾습니다 *.words. 이러한 이름의 적절한 크기 청크(크기는 xargs/ 에 의해 결정됨 ) 에 대해 find정렬된 파일에 병합됩니다 tmpfile. 파일이 이미 존재하는 경우 tmpfile(첫 번째 블록을 제외한 모든 블록에 대해) 해당 파일은 현재 블록의 다른 파일과도 병합됩니다. 파일 이름의 길이와 명령줄에 허용되는 최대 길이에 따라 내부 스크립트를 10번 이상 별도로 실행해야 할 수도 있습니다( find/는 xargs이 작업을 자동으로 수행함).

"내부" sh스크립트,

if [ -f tmpfile ]; then
    sort -o tmpfile -m tmpfile "$@"
else
    sort -o tmpfile -m "$@"
fi

출력용 sort -o tmpfile(이것도 입력이어도 tmpfile덮어쓰지 않음) 및 병합합니다. 두 가지 모두에서 스크립트에서 전달되거나 스크립트로 전달된 개별적으로 참조된 파일 이름 목록으로 확장됩니다.tmpfilesort-m"$@"findxargs

그런 다음 계속 실행하여 uniq -d모든 tmpfile중복 행을 가져옵니다.

uniq -d tmpfile >dupes.txt

"DRY" 원칙("반복하지 마십시오")을 좋아한다면 내부 스크립트를 다음과 같이 작성할 수 있습니다.

if [ -f tmpfile ]; then
    t=tmpfile
else
    t=/dev/null
fi

sort -o tmpfile -m "$t" "$@"

또는

t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"

그것은 어디에서 왔습니까?

위와 같은 이유로 를 grep -Fx -f dupes.txt *.words사용하여 이러한 중복 항목의 소스를 찾을 수 없으므로 find다시 다음을 사용합니다.

find . -type f -name '*.words' \
    -exec grep -Fx -f dupes.txt {} +

"복잡한" 처리가 필요하지 않으므로 grep에서 직접 호출 할 수 있습니다 -exec. 이 -exec옵션은 유틸리티 명령을 취하고 발견된 이름을 여기에 넣습니다 {}. 마지막으로 +현재 쉘이 지원하는 만큼의 인수가 find유틸리티의 각 호출에 배치됩니다 .{}

~이 되다완전히맞습니다. 다음 중 하나를 사용하고 싶을 수도 있습니다.

find . -type f -name '*.words' \
    -exec grep -H -Fx -f dupes.txt {} +

또는

find . -type f -name '*.words' \
    -exec grep -Fx -f dupes.txt /dev/null {} +

파일 이름이 항상 grep.

첫 번째 변형은 grep -H항상 일치하는 파일 이름을 출력하는 데 사용됩니다. 마지막 변형은 다음과 같은 사실을 사용합니다 grep.여러 파일명령줄에 제공됩니다.

grepfrom으로 전송된 마지막 파일 이름 블록에는 find실제로 파일 이름만 포함될 수 있으며, 이 경우 grep결과에 언급되지 않기 때문에 이는 중요합니다.


보상 자료:

프로파일링 find++ xargs명령 sh:

find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
    if [ -f tmpfile ]; then
        sort -o tmpfile -m tmpfile "$@"
    else
        sort -o tmpfile -m "$@"
    fi' sh 

find . -type f -name '*.words'단순히 현재 디렉터리(또는 그 아래)에서 경로 이름 목록을 생성합니다. 여기서 각 경로 이름은일반 파일( -type f) 그리고 끝에 일치하는 파일 이름 부분이 있습니다 *.words. 을 텐데현재의검색할 디렉터리는 -maxdepth 1뒤에 추가 .하거나 앞에 추가할 수 있습니다 -type f.

-print0\0발견된 모든 경로 이름이 ( nul) 문자를 구분 기호로 사용하여 출력되는지 확인합니다 . 이것은 Unix 경로에서 유효하지 않은 문자이며 개행 문자(또는 기타 이상한 내용)가 포함되어 있어도 경로 이름을 처리할 수 있게 해줍니다.

find출력을 xargs.

xargs -0A - 구분된 경로 이름 목록을 읽고 \0해당 유틸리티는 그 안의 청크를 사용하여 반복적으로 실행됩니다. 따라서 인수 목록이 너무 길다고 쉘이 불평하지 않도록 충분한 인수를 사용하여 유틸리티를 실행하십시오. 더 이상 입력이 없습니다 find.

호출된 유틸리티는 해당 플래그를 사용하여 명령줄에 문자열로 제공되는 스크립트 xargs입니다 .sh-c

후속 매개변수 와 함께 호출되면 sh -c '...some script...'이러한 매개변수를 스크립트에서 사용할 수 있습니다 $@.첫 번째 매개변수를 제외하고, 에 배치됩니다 (예를 들어, 충분히 빠른 경우 $0찾을 수 있는 "명령 이름"입니다 ). top이것이 바로 sh실제 스크립트의 마지막에 첫 번째 매개변수로 문자열을 삽입하는 이유입니다. 문자열 sh가상 논쟁임의의 단일 단어일 수 있습니다(어떤 사람들은 _또는 를 선호하는 것 같습니다 sh-find).

답변2

단일 파일 내의 행은 정렬되며 중복되지 않습니다.

이는 다음 용도를 찾을 수 있음을 의미합니다 sort -m.

 -m, --merge
        merge already sorted files; do not sort

또 다른 확실한 대안은 단순히 awk배열의 행을 수집하고 개수를 계산하는 것입니다. 그러나 ~함에 따라@DaveThompson_085의견에 따르면 이러한 30억 개의 행(또는 고유한 행이 얼마나 많든)은 저장하는 데 많은 메모리를 차지하므로 제대로 작동하지 않을 수 있습니다.

답변3

awk를 사용하면 하나의 짧은 명령으로 모든 파일의 모든 중복 줄을 가져올 수 있습니다.

$ awk '_[$0]++' *.words

그러나 행이 3번 이상 존재하면 해당 행이 중복됩니다.
첫 번째 복제본만 얻는 솔루션이 있습니다.

$ awk '_[$0]++==1' *.words

(반복 횟수가 적은 경우) 속도가 빨라야 하지만 모든 행을 메모리에 유지하기 위해 많은 메모리를 소비합니다. 실제 파일과 반복 횟수에 따라 먼저 3~4개의 파일을 시도해 보세요.

$ awk '_[$0]++==1' [123]*.words

그렇지 않으면 다음과 같이 할 수 있습니다.

$ sort -m *.words | uniq -d

그러면 고유한 반복 줄이 인쇄됩니다.

답변4

comm는 이러한 유형의 작업을 위한 또 다른 도구입니다. 유일한 주의 사항은 미리 정렬된 데이터 소스가 필요하다는 것입니다. <(...)이 구문은 대부분의 최신 쉘에서 사용할 수 있습니다.

# suppress common lines
comm -3 <(echo "1\n2") <(echo "3\n1"| sort)
2
       3


# display common lines
comm -12 <(echo "1\n2") <(echo "1\n3") 
1

관련 정보