从具有多行字段的大型 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.

的文档