我有一个命令的标准输出,我想以相反的顺序去除重复项。
也就是说,我希望从开头而不是结尾删除重复的行。例如,要从末尾剥离,我可能会使用 awk
的经典技术:
awk '!a[$0]++'
虽然很棒,但它去掉了错误的行:
$ printf 'one\nfour\ntwo\nthree\nfour\n' | awk '!a[$0]++'
one
four
two
three
我想要最后出现的 four
打印即
$ printf 'one\nfour\ntwo\nthree\nfour\n' | <script>
one
two
three
four
我该怎么做?有没有一种在 shell 中使用单行的简单方法?
最佳答案
使用您的示例生成测试输入:
printf 'one\nfour\ntwo\nthree\nfour\n'
处理此问题的最简单方法就是将数据反转两次。以下适用于 BSD 和 OS X:
command | tail -r | awk '!a[$0]++' | tail -r
但是 -r
选项不是通用的。如果您使用的是 Linux,则可以使用 tac
命令(与 cat
相反)生成相同的效果,该命令是 coreutils 的一部分:
command | tac | awk '!a[$0]++' | tac
如果这些都不起作用(即您使用的是 HP/UX 或较旧的 Solaris 等),您可以使用 sed
来逆转:
command | sed '1!G;h;$!d' | awk '!a[$0]++' | sed '1!G;h;$!d'
当然,您也可以使用 perl 来完成此操作:
command | perl -e 'print reverse <>' | awk '!a[$0]++' | perl -e 'print reverse <>'
但是如果 perl 在您的系统上可用,您还不如简化管道并完全跳过 awk:
command | perl -e '$a{$_}++ or print for reverse <>'
虽然我从来没有真正喜欢过 perl,但我确实喜欢用 shell 做事。如果您使用的是 bash(版本 4 或更高版本),并且不太关心性能,则可以直接在 shell 中实现一个数组:
mapfile -t a < <(command)
declare -A b;
for (( i=${#a[@]}-1 ; i>=0; i-- )); do ((b[${a[$i]}]++)) || echo "${a[$i]}"; done
无需外部工具。 :-)
更新:
受 sudo_O's answer 启发(或可能受到挑战) ,这是在 BSD 上以纯 awk 工作的另一种选择(即不需要 GNU awk):
command | awk '{a[NR]=$0;b[$0]=NR} END {for(i=1;i<=NR;i++) if(i==b[a[i]]) print a[i]}'
请注意,这会将所有输入存储在内存中两次,因此它可能不适合大型数据集。
关于shell - 仅打印最后出现的重复行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19258451/