删除超过 30 条相同值的记录

删除超过 30 条相同值的记录

我有一个很大的 CSV,并且想要删除具有相同名字字段 ($8)、中间名字段 ($9) 和姓氏字段 ($10) 的记录(如果有超过 30 个实例)。

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

我在参数列表中两次提到该文件,并将变量设置output1中间值。这会将代码从“计数模式”(代码中的第一个块)切换到“过滤和输出模式”(第一个块之后的测试)。

如果您需要小写您使用的密钥,我建议首先单独计算它,以提高可读性:

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 中更简单地实现一些,但恕我直言,在这种情况下,不值得损失可移植性。

相关内容