如何使用 awk 从文件中删除重复行

了解如何在不排序或更改顺序的情况下使用 awk '!visited[$0]++'。
142 位读者喜欢这篇文章。
Coding on a computer

假设你有一个文本文件,并且你需要删除其中所有重复的行。

TL;DR(太长不看)

要在保留文件中行的顺序的同时删除重复行,请使用

awk '!visited[$0]++' your_file > deduplicated_file

工作原理

该脚本维护一个关联数组,索引等于文件中唯一的行,等于它们的出现次数。对于文件的每一行,如果该行的出现次数为零,则将其出现次数增加一并打印该行,否则,仅增加出现次数而不打印该行

我对 awk 并不熟悉,我想了解如何使用如此短的脚本(有点awkward,别扭)来实现这一点。我做了一些研究,以下是它的工作原理

  • awk “脚本” !visited[$0]++ 对输入文件的每一行执行。
  • visited[]关联数组(又名 Map)类型的变量。我们不必初始化它,因为 awk 会在第一次访问时进行初始化。
  • $0 变量保存当前正在处理的行的内容。
  • visited[$0] 访问存储在 map 中的值,其键等于 $0(正在处理的行),也称为出现次数(我们在下面设置)。
  • ! 否定出现次数的值
  • ++ 操作将变量的值 (visited[$0]) 增加一。
    • 如果该值为空,awk 会自动将其转换为 0(数字),然后将其增加。
    • 注意: 该操作在我们访问变量的值之后执行。

总结一下,整个表达式的计算结果为

  • 如果出现次数为零/空字符串,则为 true(真)
  • 如果出现次数大于零,则为 false(假)

awk 语句由一个 模式表达式 和一个 关联的动作 组成。

<pattern/expression> { <action> }

如果模式成功,则执行关联的动作。如果我们不提供动作,awk 默认会 print(打印)输入。

省略的动作等效于 { print $0 }

我们的脚本由一个 awk 语句组成,该语句带有一个表达式,省略了动作。所以这

awk '!visited[$0]++' your_file > deduplicated_file

等效于这

awk '!visited[$0]++ { print $0 }' your_file > deduplicated_file

对于文件的每一行,如果表达式成功,则将该行打印到输出。否则,不执行该动作,并且不打印任何内容。

为什么不使用 uniq 命令?

uniq 命令仅删除相邻的重复行。这是一个演示

$ cat test.txt
A
A
A
B
B
B
A
A
C
C
C
B
B
A
$ uniq < test.txt
A
B
A
C
B
A

其他方法

使用 sort 命令

我们还可以使用以下 sort 命令来删除重复行,但行的顺序不会被保留

sort -u your_file > sorted_deduplicated_file

使用 cat、sort 和 cut

先前的方案将生成一个去重的文件,其行将根据内容排序。通过管道连接一堆命令 可以克服这个问题

cat -n your_file | sort -uk2 | sort -nk1 | cut -f2-

工作原理

假设我们有以下文件

abc
ghi
abc
def
xyz
def
ghi
klm

cat -n test.txt 在每一行前面加上序号。

1	abc
2	ghi
3	abc
4	def
5	xyz
6	def
7	ghi
8	klm

sort -uk2 基于第二列(k2 选项)对行进行排序,并且仅保留具有相同第二列值的行的第一次出现(u 选项)。

1	abc
4	def
2	ghi
8	klm
5	xyz

sort -nk1 基于第一列(k1 选项)对行进行排序,并将该列视为数字(-n 选项)。

1	abc
2	ghi
4	def
5	xyz
8	klm

最后,cut -f2- 打印从第二列开始到行尾的每一行(-f2- 选项:注意 - 后缀,它指示包含行的其余部分)。

abc
ghi
def
xyz
klm

参考文献

就这些。猫的照片。

Duplicate cat

本文最初出现在 Lazarus Lazaridis 的 iridakos 博客上,根据 CC BY-NC 4.0 许可协议 获得许可,并经作者许可转载。

接下来阅读什么
标签
User profile image.
我是一名软件开发人员。我在雅典经济与商业大学学习计算机科学,居住在希腊雅典。我通常使用 <strong>Ruby</strong> 编码,尤其是在 Rails 上,但我也精通 Java、Go、bash 和 C#。我热爱开源,喜欢编写教程和创建工具和实用程序。

1 条评论

我从来不喜欢 awk,但是根据我过去 2-3 年的经验,我不得不承认,每个进行复杂文本处理的人都必须了解它,因为它通常比带有其他命令的长 bash 管道更简单。

然而,有一个明显的瓶颈,那就是速度。对于大型文件来说尤其如此。
根据我的经验,我经常发现(长)管道更快,但有时 awk 单行命令占据领先地位。

在这里,我只是想看看上面的 awk 单行命令是否比管道方法更快。

我期望管道运行得更快,但事实并非如此

# 创建 1 亿个从 0 到 999999 的随机整数
cat /dev/urandom | tr -dc '0-9' | fold -w 6 | head -n100000000 >random.txt

# awk 的速度:42.99 秒用户 0.36 秒系统 99% cpu 43.355 秒总计
time (awk '!visited[$0]++' random.txt >/dev/null)

# 管道的速度:225.02 秒用户 3.17 秒系统 100% cpu 3:46.86 总计
time (awk '!visited[$0]++' random.txt >/dev/null)

结论:awk 单行命令比管道快约 5.2 倍,这显然不是微不足道的。

© . All rights reserved.