我有一个很大的 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
我在参数列表中两次提到该文件,并将变量设置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 中更简单地实现一些,但恕我直言,在这种情况下,不值得损失可移植性。