我有一个命令的stdout,我想以相反的顺序删除重复项.
也就是说,我希望重复的行从头开始而不是从末尾剥离.例如,从最后剥离我可能会使用经典技术awk:
awk '!a[$0]++'
Run Code Online (Sandbox Code Playgroud)
虽然很棒,但它会删除错误的线条:
$ printf 'one\nfour\ntwo\nthree\nfour\n' | awk '!a[$0]++'
one
four
two
three
Run Code Online (Sandbox Code Playgroud)
我想最后一次four打印即
$ printf 'one\nfour\ntwo\nthree\nfour\n' | <script>
one
two
three
four
Run Code Online (Sandbox Code Playgroud)
我该怎么做呢?在shell中有一个单线程的简单方法吗?
使用您的示例生成用于测试的输入:
printf 'one\nfour\ntwo\nthree\nfour\n'
Run Code Online (Sandbox Code Playgroud)
处理此问题的最简单方法是简单地将数据反转两次.以下适用于BSD和OS X:
command | tail -r | awk '!a[$0]++' | tail -r
Run Code Online (Sandbox Code Playgroud)
但这种-r选择并不普遍.如果您使用的是Linux,则可以使用coreutils中的tac命令(相反cat)生成相同的效果:
command | tac | awk '!a[$0]++' | tac
Run Code Online (Sandbox Code Playgroud)
如果这些都不起作用(即您使用的是HP/UX或更旧的Solaris等),您可以使用sed以下方法来解决问题:
command | sed '1!G;h;$!d' | awk '!a[$0]++' | sed '1!G;h;$!d'
Run Code Online (Sandbox Code Playgroud)
当然,您也可以使用perl执行此操作:
command | perl -e 'print reverse <>' | awk '!a[$0]++' | perl -e 'print reverse <>'
Run Code Online (Sandbox Code Playgroud)
但是如果你的系统上有perl,你可以简化管道并完全跳过awk:
command | perl -e '$a{$_}++ or print for reverse <>'
Run Code Online (Sandbox Code Playgroud)
我从来没有真正喜欢的perl,虽然和我做一样的壳做的事情.如果您使用的是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
Run Code Online (Sandbox Code Playgroud)
无需外部工具.:-)
更新:
灵感来自(或者质疑)sudo_O的答案,这里是一个纯awk的BSD上的工作多了一个选项(即不需要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]}'
Run Code Online (Sandbox Code Playgroud)
请注意,这会将所有输入存储在内存中两次,因此可能不适合大型数据集.