동일한 값을 가진 레코드를 30개 이상 삭제

동일한 값을 가진 레코드를 30개 이상 삭제

큰 CSV가 있고 인스턴스가 30개가 넘는 경우 이름 필드($8), 중간 이름 필드($9) 및 성 필드($10)가 동일한 레코드를 삭제하고 싶습니다.

TYPE|10007|44|Not Available||||CHRISTINE||HEINICKE|||49588|2014-09-15|34
TYPE|1009|44|Not Available||||ELIZABETH||SELIGMAN|||34688|2006-02-12|69
TYPE|102004|44|Not Available||||JANET||OCHS|||11988|2014-09-15|1022
TYPE|1000005|44|Not Available||||KIMBERLY||YOUNG|||1988|2016-10-04|1082

이것이 내가 지금까지 가지고 있는 것입니다:

awk -F"|" '++seen[tolower($8 || $9 || $10)] <= 30' foo.csv > newFoo.csv

답변1

나는 우리가 "간단한 CSV" 파일, 즉 포함된 구분 기호나 포함된 개행 문자가 있는 필드를 포함하지 않는 파일을 다루고 있다고 가정합니다.

30개 항목을 확인한 후 나타나는 항목 인스턴스를 삭제합니다.

awk -F '|' 'count[$8,$9,$10]++ < 30' file 

또한 이러한 항목의 처음 30개 인스턴스를 제거합니다. 위와 같은 방법을 사용하여 계산한 다음 필터링 및 출력을 위해 파일을 다시 구문 분석할 수 있습니다.

awk -F '|' '!output { ++count[$8,$9,$10]; next } count[$8,$9,$10] <= 30' file output=1 file

매개변수 목록에서 파일을 두 번 언급하고 변수를 중간 값 output으로 설정했습니다. 1그러면 코드가 "카운트 모드"(코드의 첫 번째 블록)에서 "필터 및 출력 모드"(첫 번째 블록 이후의 테스트)로 전환됩니다.

사용 중인 키를 소문자로 변경해야 하는 경우 가독성을 높이기 위해 먼저 별도로 계산하는 것이 좋습니다.

awk -F '|' '
    { key = tolower($8) SUBSEP tolower($9) SUBSEP tolower($10) }
    count[key]++ < 30' file 
awk -F '|' '
    { key = tolower($8) SUBSEP tolower($9) SUBSEP tolower($10) }
    !output { ++count[key]; next }
    count[key] <= 30' file output=1 file

값은 이 답변의 처음 두 코드 조각에서와 같이 쉼표로 구분된 키로 사용할 때 값 사이에 삽입되는 SUBSEP특수 구분 기호입니다 .awk

답변2

이는 입력이 파일에 저장되어 있고 파이프에서 나오지 않는다고 가정하고 입력 파일을 두 번 읽으려는 접근 방식일 수 있습니다.

awk -F'|' '
    { name = tolower($8 FS $9 FS $10) }
    NR==FNR { nameCnts[name]++; next }
    nameCnts[name] <= 30
' file file

또는 파이프된 입력 및 파일에서 작업할 수 있도록 파일 내용을 배열에 저장합니다.

awk -F'|' '
    {
        name = tolower($8 FS $9 FS $10)
        nameCnts[name]++
        recs[NR] = $0
        names[NR] = name
    }
    END {
        for ( recNr=1; recNr<=NR; recNr++ ) {
            name = names[recNr]
            if ( nameCnts[name] <= 30 ) {
                print recs[recNr]
            }
         }
    }
' file

또는 파이프된 입력 또는 파일에서도 작업할 수 있으면서 대용량 파일에 가장 효율적일 수 있으며 DSU(장식/정렬/장식 해제) 관용구를 사용합니다.

awk '
    BEGIN { FS=OFS="|" }
    { print tolower($8 FS $9 FS $10), $0 }
' file |
sort -t'|' -k1,3 |
awk -F '|' '
    {
        name = $1 FS $2 FS $3
        sub(/([^|]*\|){3}/,"")
    }
    name != prev {
        if ( cnt <= 30 ) printf "%s", buf
        buf = ""
        cnt = 0
        prev = name
    }
    {
        buf = buf $0 ORS
        cnt++
    }
    END  { if ( cnt <= 30 ) printf "%s", buf }
'

마지막 스크립트는 입력 순서를 유지하지 않습니다. 원하는 경우 원래 행 번호의 추가 정렬 키를 추가하여 결국 원래 순서로 다시 정렬할 수 있습니다.

위의 모든 내용은 어떤 awk에서도 작동하며 GNU awk에서는 좀 더 간단하게 구현할 수 있지만 IMHO 이 경우 이식성을 잃을 가치는 없습니다.

관련 정보