sort -u file.txt 删掉重复行并把剩下的排序;awk '!seen[$0]++' file.txt 删掉重复行并保留原来的顺序。选哪一个,取决于顺序有没有含义——在日志里它总是有的。单独的 uniq 两件事都做不到。
1. 为什么光有 uniq 不管用
$ clai 去掉 access.log 里的重复行→ uniq access.log10.0.0.710.0.0.310.0.0.710.0.0.910.0.0.310.0.0.710.0.0.1
文件原样返回了。uniq 只会合并相邻的相同行,而真实文件里的重复是散落各处的。所以才有 sort | uniq 这个经典组合。
2. 去重并排序
$ clai 去掉重复行并给文件排序→ sort -u access.log10.0.0.110.0.0.310.0.0.710.0.0.9
sort -u 一遍就把两件事都做了,比接管道给 uniq 更短也更快。
3. 去重但保留顺序
$ clai 去掉重复行但保留原来的行顺序→ awk '!seen[$0]++' access.log10.0.0.710.0.0.310.0.0.910.0.0.1
这才是它和 sort -u 的真正区别:每一行的首次出现被保留,顺序丝毫不动——这对日志以及任何按时间写入的内容都很重要。代价是内存:awk 会把所有不同的行都存住,文件到了几十 GB 就很明显。
4. 每一行出现了多少次
$ clai 统计每一行出现了多少次→ sort access.log | uniq -c | sort -rn 3 10.0.0.7 2 10.0.0.3 1 10.0.0.9 1 10.0.0.1
日志的"top N"配方。uniq -c 负责计数,后面的 sort -rn 把高频的顶上来。
5. 只看重复过的行
$ clai 只显示出现超过一次的行→ sort access.log | uniq -d10.0.0.310.0.0.7
-d 只留重复项;-u 相反,只留出现一次的行——在这个文件里是 10.0.0.1 和 10.0.0.9。比较两份清单时很好用。
6. 按某一个字段去重
$ clai 每个 URL 只保留一行→ awk '!seen[$2]++' req.logGET /a 200GET /b 404
$2 是第二个字段。按 URL、按 IP 或按会话 ID 折叠日志就是这么做的,同时完整保留首次匹配的那一行。
Gotchas
sort -u file > file会清空文件。 已验证:shell 在sort还没读之前就把文件截断为可写状态,结果是零行。正确写法是sort -u file -o file——sort 自带写回输入文件的参数,而且它是安全的。uniq需要输入是有序的。 前面不接sort,它会一声不吭地还给你几乎相同的文件。这不是报错,而是悄悄给出错误答案。- 顺序和排序是两种需求。
sort -u给的是字典序,awk '!seen[$0]++'给的是原序。日志几乎总是要后者,而文章通常只讲前者。
Related questions
怎么找出两个文件的公共行? comm -12 <(sort a.txt) <(sort b.txt)。两边输入都必须有序,进程替换正是干这个用的。
怎么忽略大小写? sort -uf 比较时不区分大小写;awk '!seen[tolower($0)]++' 效果相同,并且保留顺序。
文件大到装不进内存怎么办? sort -u 会用磁盘上的临时文件,能处理比内存更大的输入;同样的文件用 awk 版本可能把内存耗光。
See also
CliAI 会根据你的说法在排序版和保序版之间做选择,并先把命令显示出来。一行安装。