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


126

我的一个巨大的文本文件(最多2 GiB)包含其中每一行的大约100个精确重复项(对我而言,这是无用的,因为该文件是类似CSV的数据表)。

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

我已经用Scala编写了一个程序(如果您不了解Scala,请考虑使用Java)来实现此目的。但是,也许有更快的C编写的本机工具能够更快地做到这一点?

更新:awk '!seen[$0]++' filename只要文件接近2 GiB或更小,该解决方案对我来说似乎就很好,但是现在我要清理8 GiB文件就不再起作用了。在配备4 GiB RAM和配备4 GiB RAM和6 GiB交换功能的64位Windows 7 PC的Mac上,似乎占用了无限空间。鉴于这种经验,我并不热衷于在具有4 GiB RAM的Linux上进行尝试。


这将破坏您的订购,但是,您是否尝试过排序-u,我不知道它如何或是否可以在如此大的文件上运行
0x7c0 2012年

5
C通常不会比Java快很多,如果现在(按顺序)运行C,很有可能它会在您得到答案,实现它并完成运行之前完成。乱序,sort -u可能会更快。
凯文(Kevin)

Answers:


214

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

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

1
刚刚在2G文件上尝试过,在我的笔记本上花了三分钟。不错。我也尝试过uniq filename | awk'!seen [$ 0] ++',但这没有更快。
mgjk 2012年

这比awk使用2个数组查找的更详细的版本(在Gilles答案中显示为扩展说明)要快得多0m36.132s vs 0m49.958s .. 5000万行..我认为瓶颈将是I / O,但是额外的数组查找是...数组中的100万个元素似乎产生了相当大的影响...
Peter.O 2012年

但这与sort -u ....相比如何?
HashWizard '17

1
@HashWizard:此命令不进行排序,但消除了同一行的下一次出现
enzotib

1
@MaxWilliams是的,可以正常工作,因为它们是随机分布的。
setholopolus

47

有一个使用标准实用程序的简单方法(这并不是显而易见的方法),它不需要大的内存即可运行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

@Gilles根据您对的解释awk '!seen[$0]++',是否表示如果awk看到2条重复的行,它将始终保留第一行,而忽略所有后续行?(或者将保留最后一个?)
user779159

1
@ user779159保留第一个:每个输入行要么立即打印(第一次出现),要么根本不打印(重复出现)。
吉尔斯(Gilles)

但这与-u ...排序相比如何?
HashWizard '17

@HashWizard一个普通的sort -u更改顺序。我的答案显示了保留顺序(准确地说是第一次出现的顺序)的解决方案。
吉尔斯(Gilles)

@Gilles您会说,对于具有50%重复项的大文件(10G),它比sort -u更快?
HashWizard '17

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

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


1
速度不如awk其他答案中的命令,但从概念上讲很简单!
2015年

@Johann我经常在带有数十万(甚至数百万)短换行符终止字符串的文件上执行此操作。对于正在进行的实验,我很快就能得到结果。如果在反复运行的脚本中使用它可能会更重要,这样可以节省大量时间。
Vladislavs Dovgalecs

1
用于sort -u在排序过程中而不是之后删除重复项。(并节省内存带宽)将其管道传输到另一个程序)。awk如果您也希望对输出进行排序,则这仅比版本更好。(有关此问题的OP希望保留其原始顺序,因此对于用例稍有不同的情况,这是一个很好的答案。)
Peter Cordes

对我来说,花了大约一分钟时间来制作550万行文件(总计1.8 GB)。辉煌。
Max Williams

18

假设您有能力在内存中保留尽可能多的已重复数据删除的文件(如果确实确实将数据重复了100倍,那应该大约是20MiB +开销),那么使用Perl可以非常轻松地做到这一点。

$ perl -ne 'print unless $dup{$_}++;' input_file > output_file

这也保留了顺序。

%dup如果愿意,您可以从哈希中提取每一行的出现次数,以作为额外的免费奖励。

如果您愿意awk,也应该这样做(与perl版本相同的逻辑,相同的顺序,在dup变量中收集的相同数据):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file

@Mat太好了,我正要把文件lur住,大声笑;-)。
Nikhil Mulley 2012年

现在也正在等待@ManAtWork来等待他的sed和awk魔术织布工:-)
Nikhil Mulley,

awk提示再次很棒:-)
Nikhil Mulley 2012年

1
是否可以将perl脚本更改为仅删除重复的相邻行?
dumbledad '16

2
@dumbledad:uniq这一切都是由自己完成的
Mat

3

由于没有其他答案可以就地提供支持,因此这里有一个:

gawk -i inplace '!a[$0]++' file

这会保留订单吗?顺便说一句,这对我不起作用。我的版本是:GNU Awk 4.0.2
Leonid

1
@Leonid是的,确实如此。它打印任何唯一行的第一个匹配项。就地支持于2013
。– Jan Chren-rindeal


2

Python One衬板:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile

这会导致整个文件被拖入内存中,可能无法很好地解决OP的问题。也不能保证保留订单
iruvar

感谢您的建议,我一直在学习python ..只是出于学习目的尝试了这个.. :)
Rahul Patil 2013年

是Python 2.7版本,它不是单行的,而是(简洁地)返回保留行的唯一行,而无需将整个文件加载到内存中或创建单个硕大的字符串以供打印
iruvar 2013年

谢谢@ 1_CR我今天学到了一些东西:)OrderedDict
Rahul Patil

0

在我的Mac上,这里没有答案对我有用,所以我写了一个对我有用的简单python脚本。我忽略了前导/尾随空格,也不在乎内存消耗。

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

将以上内容保存到unique.py并按以下方式运行:

python unique.py inputfile.txt outputfile.txt

-1

对于bash 4,可以使用利用关联数组的纯bash解决方案。这是一个例子

unset llist; declare -A llist;
while read -r line; do
if [[ ${llist[$line]} ]]; then
  continue
else 
  printf '%s\n' "$line"
  llist[$line]="x"
fi
done < file.txt

2
不要使用read循环来处理大文本文件。bash必须一次读取一个字节,以免换行过头。与awk相比,bash通常在文本处理方面也不是很快。如果确实使用此功能,read -ra将避免在输入中吃反斜杠。另外,如果将其放在shell函数中或以交互方式使用它,也不要忘记循环unset llist 之后
彼得·科德斯

2
@PeterCordes,或者您可能刚刚引用了 :-)
iruvar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.