我有一个棘手的问题,我不知道如何解决。
我有一个包含几百万行文本的文本文件。基本上我想运行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 将后面的字段放在后面,最后的字段再次从左到右打印出来。:
uniq
FOO
OOF
:
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
:FOO
grep
$ 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
从模式空间中删除第一行并重新开始循环。