从具有多行字段的大型 csv 中删除重复行
Remove duplicates rows from large csv with multiline fields
给定以下包含多行字段的 csv:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"1","line 1
line 2"
... 显示为:
id
text
1
line 1
line 2
2
line 1
line 2
1
line 1
line 2
如果我使用以下 awk
命令根据 ID(第 1 列)从此 csv 中删除重复行:
awk -F, '!x[]++' 'file-01.csv' > 'file-01-deduped.csv'
我最终得到:
"id","text"
"1","line 1
line 2"
"2","line 1
显示为:
id
text
1
line 1
line 2
2
line 1
这是一个过于简单的示例,但似乎 awk
不能很好地处理多行字段。也许我遗漏了什么。
其他信息:我正在根据 RFC4180 standards 编写这些 csv——最值得注意的是,包含换行符、双引号和逗号的字段用双引号括起来。出现在字段内的双引号会使用前面的双引号进行转义。
此外,我正在 Node/JS 中编写 csv,但我发现 awk
是过去真正 simple/fast 对非常大的文件进行重复数据删除的方法—none 虽然有多行字段。
我绝不受 awk
的约束——我对 any/all 的建议持开放态度——只是想弄清楚我尝试了什么。谢谢!
正如其他人所指出的,您需要一个 CSV-aware 工具来正确处理行内的换行符。
GoCSV 就是为此而生的:它速度快,非常好 w/memory,精通 CSV,并且 pre-built 适用于许多平台。
其 unique subcommand 将根据一个值或一组值在一列或一组列中的出现仅保留第一行。
根据文本列删除重复行:
gocsv unique -c 'text' input.csv > de-duped.csv
它甚至可以告诉你它一路上发现了多少个骗子:
gocsv unique -c 'text' -count input.csv > de-duped.csv
多快,多好w/memory?
我模拟了一个 1_000_000 行 CSV,其中包含两列随机文本和嵌入式换行符(还包括逗号和引号):
ll -h gen_1000000x3.csv
-rw-r--r-- 1 zyoung staff 52M Apr 26 09:36 gen_1000000x3.csv
cat gen_1000000x3.csv
ID,Col1,Col2
0,"ddddd
"","" oooooo","wwwwww
"","" nnnnnnn"
1,"llllllll
"","" ccccccc","iiiiiiii
"","" wwwww"
2,"nnnnn
"","" iiiiiiii","ooooo
"","" kkkkkkkk"
...
在我的 M1 MacBook Air 上,de-duping 100 万行,52 MB CSV 占用 half-second 并且仅消耗 13 MB 内存:
/usr/bin/time -l gocsv unique -c Col2 gen_1000000x3.csv > de-duped.csv
0.45 real 0.49 user 0.05 sys
...
13124608 peak memory footprint
超过 989_000 个重复行被删除:
gocsv dims de-duped.csv
Dimensions:
Rows: 10816
Columns: 3
我们可以计算在 Col2 中找到的每个值的实例(计算消耗 175 MB 内存):
gocsv unique -c Col2 -count gen_1000000x3.csv > de-duped.csv
GoCSV 还可以在终端中显示 multi-line 行:
+--------+---------------+---------------+-------+
| ID | Col1 | Col2 | Count |
+--------+---------------+---------------+-------+
| 0 | ddddd | wwwwww | 80 |
| | "," oooooo | "," nnnnnnn | |
+--------+---------------+---------------+-------+
| 1 | llllllll | iiiiiiii | 89 |
| | "," ccccccc | "," wwwww | |
+--------+---------------+---------------+-------+
| 2 | nnnnn | ooooo | 97 |
| | "," iiiiiiii | "," kkkkkkkk | |
...
我无法比较到目前为止建议的 awk 脚本:一个在我的终端中什么都不做,另一个需要 GNU,而我没有。但是 awk 会变慢:运行 awk '{print [=17=]}' gen_1000000x3.csv > /dev/null
的时间要长 3 倍,而且这甚至没有做有意义的工作。您必须克服重重困难才能尝试从头开始编写 CSV 解析器。
Awk 不支持 csv,因此它并不是真正适合这项工作的工具。互联网上流传着一些 csv 实现,也许你可以看看它们。
你确实提到文件很大,但如果它符合你的记忆,这就是几周前我需要的东西的变体。它是使用 FPAT
的 GNU awk,所以它不是很快:
$ gawk '
BEGIN {
RS="^$" # read in whole file
FPAT="([^,\n]*)|(\"(\"\"|[^\"])+\")" # regex magic
OFS=","
}
{
for(i=1;i<NF;i+=2) # iterate fields 2 at a time
if(!a[$i]++) # if first field not seen before
print $i,$(i+1) # output 2 fields
}' file
测试数据:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"3"," ""line 1""
line 2"
"4",""
"5","line 1,
line 2"
"1","line 1
line 2"
输出:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"3"," ""line 1""
line 2"
"4",""
"5","line 1,
line 2"
不过,我不知道有多少方法会让你失望。
仅使用您显示的示例,请尝试以下 awk
代码。在 GNU awk
中编写和测试,应该在任何 awk
.
中工作
awk -F',' '
FNR>1{
sub(/^"/,"",)
sub(/"$/,"",)
gsub(/"/,"",)
print OFS ORS " "
}
' <(awk '{printf("%s%s",[=10=]!~/^"/?",":FNR>1?ORS:"",[=10=])} END{print ""}' Input_file)
解释: 简单的解释是,运行 第一个 awk
将所有行打印在一行中(只要它的行不是从 "
开始的)并将其输出作为输入发送到 main awk
,其中根据要求打印所需的 id 值和所有行值。
CSV 感知工具是一个很棒且非常简单的工具是 Miller。
运行
mlr --csv uniq -a input.csv >output.csv
你将拥有
id,text
1,"line 1
line 2"
2,"line 1
line 2"
它还有一个很棒的文档:这是 uniq
verb.
的文档
给定以下包含多行字段的 csv:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"1","line 1
line 2"
... 显示为:
id | text |
---|---|
1 | line 1 line 2 |
2 | line 1 line 2 |
1 | line 1 line 2 |
如果我使用以下 awk
命令根据 ID(第 1 列)从此 csv 中删除重复行:
awk -F, '!x[]++' 'file-01.csv' > 'file-01-deduped.csv'
我最终得到:
"id","text"
"1","line 1
line 2"
"2","line 1
显示为:
id | text |
---|---|
1 | line 1 line 2 |
2 | line 1 |
这是一个过于简单的示例,但似乎 awk
不能很好地处理多行字段。也许我遗漏了什么。
其他信息:我正在根据 RFC4180 standards 编写这些 csv——最值得注意的是,包含换行符、双引号和逗号的字段用双引号括起来。出现在字段内的双引号会使用前面的双引号进行转义。
此外,我正在 Node/JS 中编写 csv,但我发现 awk
是过去真正 simple/fast 对非常大的文件进行重复数据删除的方法—none 虽然有多行字段。
我绝不受 awk
的约束——我对 any/all 的建议持开放态度——只是想弄清楚我尝试了什么。谢谢!
正如其他人所指出的,您需要一个 CSV-aware 工具来正确处理行内的换行符。
GoCSV 就是为此而生的:它速度快,非常好 w/memory,精通 CSV,并且 pre-built 适用于许多平台。
其 unique subcommand 将根据一个值或一组值在一列或一组列中的出现仅保留第一行。
根据文本列删除重复行:
gocsv unique -c 'text' input.csv > de-duped.csv
它甚至可以告诉你它一路上发现了多少个骗子:
gocsv unique -c 'text' -count input.csv > de-duped.csv
多快,多好w/memory?
我模拟了一个 1_000_000 行 CSV,其中包含两列随机文本和嵌入式换行符(还包括逗号和引号):
ll -h gen_1000000x3.csv
-rw-r--r-- 1 zyoung staff 52M Apr 26 09:36 gen_1000000x3.csv
cat gen_1000000x3.csv
ID,Col1,Col2
0,"ddddd
"","" oooooo","wwwwww
"","" nnnnnnn"
1,"llllllll
"","" ccccccc","iiiiiiii
"","" wwwww"
2,"nnnnn
"","" iiiiiiii","ooooo
"","" kkkkkkkk"
...
在我的 M1 MacBook Air 上,de-duping 100 万行,52 MB CSV 占用 half-second 并且仅消耗 13 MB 内存:
/usr/bin/time -l gocsv unique -c Col2 gen_1000000x3.csv > de-duped.csv
0.45 real 0.49 user 0.05 sys
...
13124608 peak memory footprint
超过 989_000 个重复行被删除:
gocsv dims de-duped.csv
Dimensions:
Rows: 10816
Columns: 3
我们可以计算在 Col2 中找到的每个值的实例(计算消耗 175 MB 内存):
gocsv unique -c Col2 -count gen_1000000x3.csv > de-duped.csv
GoCSV 还可以在终端中显示 multi-line 行:
+--------+---------------+---------------+-------+
| ID | Col1 | Col2 | Count |
+--------+---------------+---------------+-------+
| 0 | ddddd | wwwwww | 80 |
| | "," oooooo | "," nnnnnnn | |
+--------+---------------+---------------+-------+
| 1 | llllllll | iiiiiiii | 89 |
| | "," ccccccc | "," wwwww | |
+--------+---------------+---------------+-------+
| 2 | nnnnn | ooooo | 97 |
| | "," iiiiiiii | "," kkkkkkkk | |
...
我无法比较到目前为止建议的 awk 脚本:一个在我的终端中什么都不做,另一个需要 GNU,而我没有。但是 awk 会变慢:运行 awk '{print [=17=]}' gen_1000000x3.csv > /dev/null
的时间要长 3 倍,而且这甚至没有做有意义的工作。您必须克服重重困难才能尝试从头开始编写 CSV 解析器。
Awk 不支持 csv,因此它并不是真正适合这项工作的工具。互联网上流传着一些 csv 实现,也许你可以看看它们。
你确实提到文件很大,但如果它符合你的记忆,这就是几周前我需要的东西的变体。它是使用 FPAT
的 GNU awk,所以它不是很快:
$ gawk '
BEGIN {
RS="^$" # read in whole file
FPAT="([^,\n]*)|(\"(\"\"|[^\"])+\")" # regex magic
OFS=","
}
{
for(i=1;i<NF;i+=2) # iterate fields 2 at a time
if(!a[$i]++) # if first field not seen before
print $i,$(i+1) # output 2 fields
}' file
测试数据:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"3"," ""line 1""
line 2"
"4",""
"5","line 1,
line 2"
"1","line 1
line 2"
输出:
"id","text"
"1","line 1
line 2"
"2","line 1
line 2"
"3"," ""line 1""
line 2"
"4",""
"5","line 1,
line 2"
不过,我不知道有多少方法会让你失望。
仅使用您显示的示例,请尝试以下 awk
代码。在 GNU awk
中编写和测试,应该在任何 awk
.
awk -F',' '
FNR>1{
sub(/^"/,"",)
sub(/"$/,"",)
gsub(/"/,"",)
print OFS ORS " "
}
' <(awk '{printf("%s%s",[=10=]!~/^"/?",":FNR>1?ORS:"",[=10=])} END{print ""}' Input_file)
解释: 简单的解释是,运行 第一个 awk
将所有行打印在一行中(只要它的行不是从 "
开始的)并将其输出作为输入发送到 main awk
,其中根据要求打印所需的 id 值和所有行值。
CSV 感知工具是一个很棒且非常简单的工具是 Miller。 运行
mlr --csv uniq -a input.csv >output.csv
你将拥有
id,text
1,"line 1
line 2"
2,"line 1
line 2"
它还有一个很棒的文档:这是 uniq
verb.