删除几乎重复的行

删除几乎重复的行

我有一个棘手的问题,我不知道如何解决。

我有一个包含几百万行文本的文本文件。基本上我想运行uniq,但有一个转折:如果两行相同但有后缀:FOO,则删除缺少后缀的行。但仅有的如果这些行在其他方面是相同的。和仅有的for :FOO,而不是任何其他可能的后缀。做不是想要删除/usr/bin/delta:FOO,因为上面的行不相同。

red.7
green.2
green.2:FOO
blue.6
yellow.9:FOO

我想删除green.2,因为下面的行是相同的,但带有后缀。所有其他行应保留不变。

[编辑:我忘记说了,文件已经按顺序排列了。]

到目前为止我的想法:

  • 显然uniq是执行此操作的工具。
  • 你可以uniq忽略一个字首,但从来没有后缀。 (这非常烦人!)
  • 我想也许你可以假装这:是一个字段分隔符,并cut(与paste)一起翻转字段顺序。但是不,cut如果不存在分隔符,显然不可能强制输出空行。
  • 我的下一个想法是逐行浏览并根据后缀是否存在输出 1 个字符的前缀...但我无法想象将其编写为高性能的 Bash 循环。

有什么提示吗?

我可能最终只使用真实的编程语言来解决这个问题。在 Bash 中修复它似乎很简单,但我已经浪费了很多时间未能让它工作......

答案1

在最简单的情况下,要保留不带 的行:FOO,您可以删除:FOO然后通过 uniq:

$ sed 's/:FOO$//' file | uniq
red.7
green.2
blue.6
yellow.9

如果您更喜欢保留这些:FOO行并假设它们总是在无后缀的兄弟之后,您可以尝试:

$ rev file | sed 's/:/ /' | uniq -f1 | sed 's/ /:/' | rev
red.7
green.2:FOO
blue.6
yellow.9:FOO

rev从右到左打印每一行。用空格替换第一个字段,因此可以sed使用recognize (或,在本例中)作为应忽略的第一个字段,下一个 sed 将后面的字段放在后面,最后的字段再次从左到右打印出来。:uniqFOOOOF:rev


不幸的是,尽管它的文档声称,uniq它不仅使用空格和制表符作为字段分隔符,而且几乎使用任何非字母数字字符:

$ printf 'foo/1\nfoo/2\nfoo%%3\nfoo:4\n' 
foo/1
foo/2
foo%3
foo:4
$ printf 'foo/1\nfoo/2\nfoo%%3\nfoo:4\n'  | uniq -f1
foo/1

这意味着如果您有这样的字符,上面的解决方案将不起作用。作为替代方案,您可以对于文件中grep的所有实例,删除并将结果作为模式列表提供给新的,以避免::FOO:FOOgrep

$ grep -hFxv "$(grep ':FOO' file | cut -d: -f1)" file 
red.7
green.2:FOO
blue.6
yellow.9:FOO

答案2

一种方法awk

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file

保存该行,然后在每行的开头检查它是否不包含保存的字符串 + :FOO。打印最后一行,因为下一行不可能有,:FOO因为没有。

答案3

如何连接相邻的行对,然后使用反向引用来查找非唯一前缀?

$ sed '$!N; /\(.*\)\n\1:FOO/D; P;D' file
red.7
green.2:FOO
blue.6
yellow.9:FOO

解释:

  • $!N- 如果我们还没有到达最后一行,则将下一行附加到模式空间,并用换行符分隔
  • /\(.*\)\n- 将所有内容匹配到换行符(即每对行的第一行)并将其保存到捕获组
  • \1:FOO现在匹配从第一行捕获的任何内容,后跟:FOO(\1反向引用到第一个捕获组)
  • /\(.*\)\n\1:FOO/D- 如果每对的第二行与第一行相同,后跟:FOO,则D删除第一行
  • P打印并D删除剩余的行,准备开始下一个周期

或更整洁(感谢@don_crissti)

 sed '$!N; /\(.*\)\n\1:FOO/!P;D' file

N意味着模式空间中总是有两行连续的行,并且P仅当第二行与第一行加上后缀 不同时,sed 才会打印其中的第一行:FOO。然后D从模式空间中删除第一行并重新开始循环。

相关内容