2

我从一个命令中获得了标准输出,我想以相反的顺序删除重复项。

也就是说,我希望从头开始而不是从结尾剥离重复的行。例如,要从最后剥离,我可能会使用经典技术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

我该怎么做呢?有没有一种简单的方法在外壳中使用单线?

4

2 回答 2

5

使用您的示例生成用于测试的输入:

printf 'one\nfour\ntwo\nthree\nfour\n'

处理此问题的最简单方法是将数据反转两次。以下适用于 BSD 和 OS X:

command | tail -r | awk '!a[$0]++' | tail -r

但该-r选项并不通用。如果您使用的是 Linux,您可以使用coreutils 中的tac命令(与 的相反)生成相同的效果:cat

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]}'

请注意,这会将所有输入存储在内存中两次,因此它可能不适合大型数据集。

于 2013-10-08T21:13:05.330 回答
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
于 2013-10-08T21:22:11.740 回答