假设你有一个文本文件,并且你需要删除其中所有重复的行。
TL;DR(太长不看)
要在保留文件中行的顺序的同时删除重复行,请使用
awk '!visited[$0]++' your_file > deduplicated_file
工作原理
该脚本维护一个关联数组,索引等于文件中唯一的行,值等于它们的出现次数。对于文件的每一行,如果该行的出现次数为零,则将其出现次数增加一并打印该行,否则,仅增加出现次数而不打印该行。
我对 awk 并不熟悉,我想了解如何使用如此短的脚本(有点awkward,别扭)来实现这一点。我做了一些研究,以下是它的工作原理
- awk “脚本” !visited[$0]++ 对输入文件的每一行执行。
- visited[] 是 关联数组(又名 Map)类型的变量。我们不必初始化它,因为 awk 会在第一次访问时进行初始化。
- $0 变量保存当前正在处理的行的内容。
- visited[$0] 访问存储在 map 中的值,其键等于 $0(正在处理的行),也称为出现次数(我们在下面设置)。
- ! 否定出现次数的值
- 在 awk 中,任何非零数值或任何非空字符串值都为真。
- 默认情况下,变量被初始化为空字符串,如果转换为数字,则为零。
- 话虽如此
- 如果 visited[$0] 返回一个大于零的数字,则此否定解析为 false(假)。
- 如果 visited[$0] 返回一个等于零的数字或一个空字符串,则此否定解析为 true(真)。
- ++ 操作将变量的值 (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
参考文献
就这些。猫的照片。

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