仅打印最后一次出现的重复行

时间:2013-10-08 21:08:11

标签: shell unix awk

我有一个命令的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中有一个简单的单行方式吗?

2 个答案:

答案 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