如何删除文本文件中的重复行?

如何删除文本文件中的重复行?

我的一个巨大(最大 2 GiB)文本文件包含大约 100 个每行的精确重复项(在我的例子中没用,因为该文件是一个类似 CSV 的数据表)。

我需要的是删除所有重复,同时(最好是这样,但这可能会为了显着的性能提升而牺牲)保持原始序列顺序。在结果中,每一行都是唯一的。如果有 100 条相同的行(通常重复的行分布在文件中并且不会是邻居),则只会留下其中的一种。

我用 Scala 编写了一个程序(如果您不了解 Scala,请考虑使用 Java)来实现此功能。但也许有更快的 C 编写的本机工具能够更快地完成此操作?

更新:awk '!seen[$0]++' filename只要文件接近 2 GiB 或更小,该解决方案似乎对我来说就很好用,但现在当我要清理 8 GiB 文件时,它不再起作用了。在具有 4 GiB RAM 的 Mac 和具有 4 GiB RAM 和 6 GiB 交换的 64 位 Windows 7 PC 上,似乎无穷无尽,只是内存不足。考虑到这次经历,我对在具有 4 GiB RAM 的 Linux 上尝试它并不热心。

答案1

awk#bash (Freenode) 上看到的解决方案:

awk '!seen[$0]++' filename

如果要就地编辑文件,可以使用以下命令(前提是您使用实现此扩展的 GNU awk 版本):

awk -i inplace '!seen[$0]++' filename

答案2

有一个使用标准实用程序的简单(并不是说显而易见)的方法,除了运行之外不需要大量内存sort,在大多数实现中,它对大文件有特定的优化(一个很好的外部排序算法)。此方法的优点是它仅循环专用实用程序内的所有行,而不会循环遍历解释语言内的所有行。

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

如果所有行都以非空白字符开头,则可以省略一些选项:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

对于大量重复,仅需要在内存中存储每行的单个副本的方法会表现更好。通过一些解释开销,有一个非常简洁的 awk 脚本(已经由 enzotib 发表):

<input awk '!seen[$0]++'

不太简洁:!seen[$0] {print} {seen[$0] += 1},即如果尚未看到当前行,则打印当前行,然后增加seen该行的计数器(未初始化的变量或数组元素的数值为 0)。

对于长行,您可以通过仅保留每行的不可欺骗的校验和(例如加密摘要)来节省内存。例如,使用 SHA-1,每行只需要 20 个字节加上恒定的开销。但计算摘要的速度相当慢;仅当您拥有快速 CPU(尤其是具有硬件加速器来计算摘要的 CPU)并且相对于文件大小和足够长的行而言内存不多时,此方法才会获胜。没有基本的实用程序可以让您计算每行的校验和;你必须承担 Perl/Python/Ruby/… 的解释开销,或者编写专用的编译程序。

<input perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output

答案3

sort -u big-csv-file.csv > duplicates-removed.csv

请注意,输出文件将被排序。

答案4

gawk -i inplace '!a[$0]++' SOME_FILE [SOME_OTHER_FILES...]

此命令会过滤掉重复的行,同时保留其顺序,并将文件保存在适当的位置。

它通过保留所有唯一行的缓存并仅打印每行一次来完成此任务。

确切的算法可以分解为:

  1. 将当前行存储在变量中$0
  2. 检查关联数组是否a有键$0,如果没有,则创建键并将其值初始化为0
  3. 比较键的值,0如果为 true,则打印当前行
  4. 将键的值增加1
  5. 获取下一行并转到步骤 1。直到到达 EOF

或作为伪代码:

while read $line
do
    $0 := $line
    if not a.has_key($0) :
        a[$0] := 0
    if a[$0] == 0 :
        print($line)
    a[$0] := a[$0] + 1
done

注意:该命令需要 2013 年或更高版本的 GNU AWK 版本 4.1

相关内容