TranslateProject/translated/tech/20191028 How to remove duplicate lines from files with awk.md

243 lines
6.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#]: collector: (lujun9972)
[#]: translator: (lxbwolf)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (How to remove duplicate lines from files with awk)
[#]: via: (https://opensource.com/article/19/10/remove-duplicate-lines-files-awk)
[#]: author: (Lazarus Lazaridis https://opensource.com/users/iridakos)
怎样使用 awk 删掉文件中重复的行
======
学习怎样使用 awk 的 `!visited[$0]++` 在不重新排序或改变原排列顺序的前提下删掉重复的行。
![Coding on a computer][1]
假设你有一个文本文件,你需要删掉所有重复的行。
### 这篇内容篇幅比较长,如果不想深入探讨或时间有限,可以看总结。
*保持原来的排列顺序*删掉重复行,使用:
```
`awk '!visited[$0]++' your_file > deduplicated_file`
```
### 工作原理
这个脚本维持一个关联数组,*index 总数*为文件去重后的行数,每个 index 对应的 *value* 为某行出现的次数。对于文件的每一行,如果这行出现的次数为 0则 value 加 1 *并打印这行*,否则 value 加 1 *不打印这行*
我之前不熟悉 **awk**,我想弄清楚这么短小的一个脚本是怎么实现的。我调研了下,下面是调研心得:
* awk “脚本” **!visited[$0]++** 对输入文件的*每一行*都执行
* **visited[]** 是一个 [关联数组][2] (又名 [Map][3])的变量。 **awk** 会在第一次执行时初始化它,因此我们不需要初始化。
* **$0** 变量的值是当前正在被处理的行的内容
* **visited[$0]** 通过与 **$0**(正在被处理的行)相等的 key 访问 map 中的值,即出现次数(我们在下面设置的)
* **!** 对表示出现次数的值取反
* 在 awk 中,[任意非零的数或任意非空的字符串的值是 true][4]。
* [变量默认的初始值为空字符串][5],如果被转换为数字,则为 0.
* 也就是说:
* 如果 **visited[$0]** 的值是一个比 0 大的数,取反后被解析成 **false**
* 如果 **visited[$0]** 的值为等于 0 的数字或空字符串,取反后被解析成 **true**
* **++** 表示变量visited[$0])的值加 1.
* 如果值为空,**awk** 自动把它转换为 **0**(数字) 后加 1。
* **注意:**加 1 操作是在我们取到了变量的值之后执行的。
总的来说,整个表达式的意思是:
* **true** 如果表示出现次数的值为 0 或空字符串
* **false** 如果出现的次数大于 0
**awk** 由 [_pattern 表达式和一个与之关联的 action_][6] 组成
```
`<pattern/expression> { <action> }`
```
如果匹配到了 pattern就会执行后面的 action。如果没有 action**awk** 默认会 **print** 输入。
> 省略 action 等于 **{print $0}**。
我们的脚本由一个 **awk** 表达式语句组成,省略了 action。因此这样写
```
`awk '!visited[$0]++' your_file > deduplicated_file`
```
等于这样写:
```
`awk '!visited[$0]++ { print $0 }' your_file > deduplicated_file`
```
对于文件的每一行,如果表达式匹配到了,这行内容被 print 到输出。否则,不执行 action不打印任何东西。
### 为什么不用 uniq 命令?
**uniq** 命令仅能对相邻的行去重。这是一个示例:
```
$ cat test.txt
A
A
A
B
B
B
A
A
C
C
C
B
B
A
$ uniq &lt; test.txt
A
B
A
C
B
A
```
### 其他方法
#### 使用 sort 命令
我们也可以用下面的 [**sort**][7] 命令来去除重复的行,但是*行原来的顺序没有被保留*。
```
`sort -u your_file > sorted_deduplicated_file`
```
#### 使用 cat + sort + cut
上面的方法会产出一个去重的文件,各行是基于内容进行排序的。[通过管道连接命令][8] 可以解决这个问题。
```
`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
```
### 参考
* [GNU awk 用户手册][9]
* [awk 中的数组][2]
* [Awk—Truth values][4]
* [Awk 表达式][5]
* [Unix 怎么删除文件中重复的行?][10]
* [不用排序去掉重复的行【去重】][11]
* ['!a[$0]++' 工作原理][12]
以上为全文。附上猫照。
![Duplicate cat][13]
* * *
本文首发在 [Lazarus Lazaridis][14] 的 iridakos 博客,遵照 [CC BY-NC 4.0 License][15] ,转载已获得作者授权。
--------------------------------------------------------------------------------
via: https://opensource.com/article/19/10/remove-duplicate-lines-files-awk
作者:[Lazarus Lazaridis][a]
选题:[lujun9972][b]
译者:[lxbwolf](https://github.com/lxbwolf)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/iridakos
[b]: https://github.com/lujun9972
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/code_computer_laptop_hack_work.png?itok=aSpcWkcl (Coding on a computer)
[2]: http://kirste.userpage.fu-berlin.de/chemnet/use/info/gawk/gawk_12.html
[3]: https://en.wikipedia.org/wiki/Associative_array
[4]: https://www.gnu.org/software/gawk/manual/html_node/Truth-Values.html
[5]: https://ftp.gnu.org/old-gnu/Manuals/gawk-3.0.3/html_chapter/gawk_8.html
[6]: http://kirste.userpage.fu-berlin.de/chemnet/use/info/gawk/gawk_9.html
[7]: http://man7.org/linux/man-pages/man1/sort.1.html
[8]: https://stackoverflow.com/a/20639730/2292448
[9]: https://www.gnu.org/software/gawk/manual/html_node/
[10]: https://stackoverflow.com/questions/1444406/how-can-i-delete-duplicate-lines-in-a-file-in-unix
[11]: https://stackoverflow.com/questions/11532157/remove-duplicate-lines-without-sorting
[12]: https://unix.stackexchange.com/questions/159695/how-does-awk-a0-work/159734#159734
[13]: https://opensource.com/sites/default/files/uploads/duplicate-cat.jpg (Duplicate cat)
[14]: https://iridakos.com/about/
[15]: http://creativecommons.org/licenses/by-nc/4.0/