shell - 仅打印最后出现的重复行

标签 shell unix awk

我有一个命令的标准输出,我想以相反的顺序去除重复项。

也就是说,我希望从开头而不是结尾删除重复的行。例如,要从末尾剥离,我可能会使用 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/

相关文章:

bash - 在没有输入的情况下运行 bash 脚本

arrays - 将包含空格的 grep 输出存储在数组中

linux - 如何从命令行访问 Unix 域套接字?

linux - 删除每 4 行末尾的逗号

linux - 如何在Shell脚本中对数组进行硬编码?

awk 与条件的比较

string - 在 UNIX 中,如何在长字符串中找到单个单词?

bash - bash 脚本中的 Sed/Awk

linux - `cp -dR ` 的问题

linux - 作为并行作业循环遍历文件夹中的每个脚本,