去除重复行但不排序

157
我可以担任翻译工作。以下是您需要翻译的内容:

我有一个Python实用脚本:


#!/usr/bin/env python
import sys
unique_lines = []
duplicate_lines = []
for line in sys.stdin:
  if line in unique_lines:
    duplicate_lines.append(line)
  else:
    unique_lines.append(line)
    sys.stdout.write(line)
# optionally do something with duplicate_lines

这种简单的功能(uniq不需要先排序,具有稳定排序)必须作为一个简单的UNIX实用程序提供,是不是?也许可以通过管道中的过滤器组合来实现?

提出问题的原因:我需要在无法从任何地方执行Python的系统上使用此功能。


4
无关联:在那个Python脚本中,你应该使用一个set而不是一个list;在列表中查找成员是一个线性时间操作。 - Nicholas Riley
我已从您的标签和标题中删除了“Python”,因为这与Python没有任何关系。 - Michael Hoffman
1
如果必须使用Python来完成这个任务,更好的方法是使用uniq_everseen itertools配方:http://docs.python.org/library/itertools.html#recipes - iruvar
8个回答

378

UNIX Bash脚本博客建议:

awk '!x[$0]++'

这个命令告诉awk打印哪些行。变量$0保存整行的内容,方括号用于数组访问。因此,对于文件的每一行,数组x的节点会递增,并且只有当该节点的内容之前没有被设置(!)时,才会打印该行。


12
对于像这样的简短的awk语句(没有大括号),该命令只是告诉awk要打印哪些行。变量$0保存一行的全部内容,方括号是数组访问符号。因此,对于文件的每一行,我们都会增加名为x的数组节点,并在该节点的内容以前未设置时打印该行。 - Jeff Klukas
11
我发现这是我遇到的最紧凑、最精美的脚本。致敬! - Dhaval Patel
51
给新手留下 awk 语法就像是乱码的印象,命名数组为 seen 而不是 x 可以减少这种困惑。 - Josip Rodin
7
请记住,这将会把整个文件加载到内存中,所以不要在没有足够RAM的情况下尝试对一个大小为3GB的文本文件进行操作。 - Hitechcomputergeek
10
这并不一定会把整个文件加载到内存中,只会加载独特的行。当然,如果所有行都是独特的,那么可能会加载整个文件。 - deltaray
显示剩余15条评论

101

晚来的回答 - 我刚遇到了一个与此重复的问题 - 但也许值得补充一下...

@1_CR答案背后的原则可以更简洁地写成使用cat -n而不是awk添加行号:

cat -n file_name | sort -uk2 | sort -n | cut -f2-
  • 使用cat -n命令添加行号。
  • 使用sort -u命令去除重复数据(-k2表示从第二个字段开始排序)。
  • 使用sort -n命令按添加的行号排序。
  • 使用cut命令删除行号(-f2-表示选择第二个字段到末尾的部分)。

4
易于理解,这通常很有价值。对于大文件的性能,您有任何想法可以与上面Michael Hoffman提供的最短解决方案进行比较吗? - Sopalajo de Arrierez
3
更易读/易维护。需要相同的内容,但是使用反向排序来仅保留每个唯一值的最后一个出现。在同一排序命令中同时使用“--reverse”和“--unique”不会返回预期结果。显然,sort通过首先在输入上应用“--unique”(以减少后续步骤中的处理)进行了过早的优化。这会过早地删除“--reverse”步骤所需的数据。 为了解决这个问题,在管道中插入一个“sort --reverse -k2”作为第一个排序: cat -n file_name | sort -rk2 | sort -uk2 | sort -nk1 | cut -f2- - Petru Zaharia
2
只用了60秒,就处理完一个900MB+的文本文件,其中有很多(随机放置的)重复行,结果只有39KB。速度足够快。 - ynn
3
保留最后一个匹配项的"管道"版本:cat file_name | cat -n | sort -rk2 | sort -uk2 | sort -nk1 | cut -f2- - Victor Yarema
如果您正在对临时排序的行进行其他操作,将行号添加到每行末尾可能更有用。 cat 无法本地执行此操作,但插入 rev 管道可以:rev file_name | cat -n | rev | sort | my_complicated.sh | rev | sort -n | cut -f2- | rev - ATLief
显示剩余3条评论

10

从两个文件中删除重复项:

awk '!a[$0]++' file1.csv file2.csv

5

迈克尔·霍夫曼的解决方案简洁明了。对于较大的文件,使用Schwartzian转换方法,涉及使用awk添加索引字段,然后进行多轮排序和去重,可以减少内存开销。以下代码片段适用于bash。

awk '{print(NR"\t"$0)}' file_name | sort -t$'\t' -k2,2 | uniq --skip-fields 1 | sort -k1,1 -t$'\t' | cut -f2 -d$'\t'

这似乎相当缓慢。 - galois

5
现在您可以查看用Rust编写的这个小工具:uq
它执行去重操作而无需首先对输入进行排序,因此可应用于连续流数据。
相较于得票最多的awk解决方案和其他基于shell的解决方案,该工具有两个优点:
  1. uq使用哈希值记住行的出现次数,因此当行很长时,它不会占用太多内存。
  2. uq可以通过设置要存储的条目数量限制来保持内存使用量恒定(达到限制时,有一个标志来控制是覆盖还是停止),而awk解决方案在行数过多时可能会遇到OOM问题。

1
鉴于awk已经可以做到这一点,这样做非常不方便且不够便携。 - ahmet alp balkan
哈哈,我昨晚只是想写一段有关这个的 Rust 代码。 - tink

2
感谢1_CR!我需要“uniq -u”(完全删除重复项)而不是uniq(保留重复项的1个副本)。 awk和perl的解决方案不能真正修改以执行此操作,但您的可以!我还可能需要更低的内存使用率,因为我将uniq'ing约100,000,000行8-)。 以防其他人需要,我只是在命令的uniq部分中加入了"-u"。
awk '{print(NR"\t"$0)}' file_name | sort -t$'\t' -k2,2 | uniq -u --skip-fields 1 | sort -k1,1 -t$'\t' | cut -f2 -d$'\t'

-1

我只想在以下行中删除所有重复项,而不是在整个文件中。所以我使用了:

awk '{
  if ($0 != PREVLINE) print $0;
  PREVLINE=$0;
}'

12
uniq就是做那个的,不是吗? - Mischa Molhoek

-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接