我今天遇到了这个用例。乍一看似乎很简单,但是摆弄sort
、uniq
、sed
就会awk
发现它并不简单。
怎么才能全部删除对重复行?换句话说,如果给定行有偶数个重复项,则将其全部删除;如果有奇数个重复行,则删除除一行之外的所有行。 (可以假设已排序的输入。)
干净优雅的解决方案是更好的选择。
输入示例:
a
a
a
b
b
c
c
c
c
d
d
d
d
d
e
输出示例:
a
d
e
答案1
sed
我在发布这个问题后不久就找到了答案;sed
到目前为止还没有其他人使用过,所以这里是:
sed '$!N;/^\(.*\)\n\1$/d;P;D'
稍微解决一下更普遍的问题(删除三行、四行或五行怎么样?)提供了以下可扩展的解决方案:
sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp
扩展以删除三重行:
sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp
或者删除四边形线:
sed -e ':top' -e '$!{/\n.*\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1\n\1$/d;P;D' temp
sed
与大多数其他选项相比,它还有一个额外的优势,那就是它能够真正在流中操作,不需要比要检查重复的实际行数更多的内存存储。
作为cuonglm在评论中指出,将区域设置设置为 C 是必要的,以避免无法正确删除包含多字节字符的行。所以上面的命令就变成了:
LC_ALL=C sed '$!N;/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp
# Etc.
答案2
它不是很优雅,但它是我能想到的最简单的:
uniq -c input | awk '{if ($1 % 2 == 1) { print substr($0, 9) }}'
substr() 只是修剪输出uniq
。这将一直有效,直到一行的重复次数超过 9,999,999 个(在这种情况下,uniq 的输出可能会溢出 9 个字符)。
答案3
尝试一下awk
下面的脚本:
#!/usr/bin/awk -f
{
if ((NR!=1) && (previous!=$0) && (count%2==1)) {
print previous;
count=0;
}
previous=$0;
count++;
}
END {
if (count%2==1) {
print previous;
}
}
假设lines.txt
文件已排序。
考试:
$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e
答案4
如果输入已排序:
perl -0pe 'while(s/^(.*)\n\1\n//m){}'