我有一个命令的stdout,我想以相反的顺序删除重复项。
也就是说,我希望重复的行从头开始而不是从末尾剥离。例如,要从最后剥离,我可以使用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中有一个简单的单行方式吗?
答案 0 :(得分:5)
使用您的示例生成测试输入:
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]}'
请注意,这会将所有输入存储在内存中两次,因此可能不适合大型数据集。
答案 1 :(得分:2)
在实践中,我会使用 ghoti 技术(rev
),但这里有一个GNU awk
脚本可以打印最后一个出现:
command | awk '{a[$0]=NR;b[NR]=$0}END{n=asort(a);for(i=1;i<=n;i++)print b[a[i]]}'
one
two
three
four