问题产生背景

自己写的一个基于 FastDFS 的客户端程序的日志格式如下:

[2013-09-06 08:57:01] 1884096 group6/M00/00/2D/Kj4ZKlIpKL6Actm8ABy_wPYwpa8782.mp3 ;fuckgfw.com/mp3k18/a2/1375_8767.mp3  
[2013-09-06 08:57:01] 1932032 group6/M00/00/2D/Kj4ZKlIpKL6APMlMAB17AFl-Zaw344.mp3 ;fuckgfw.com/mp3k18/a2/1390_20402.mp3
[2013-09-06 08:57:01] 2115392 group6/M00/00/28/Kj4ZK1IpKL6AUW6WACBHQHAveu0805.mp3 ;fuckgfw.com/mp3k18/a2/1381_8842.mp3  
[2013-09-06 08:57:01] 2340800 group6/M00/00/28/Kj4ZK1IpKL-ABGh8ACO3wLWZNXA955.mp3 ;fuckgfw.com/mp3k18/a2/1395_9009.mp3  
[2013-09-06 08:57:01] 1734272 group6/M00/00/28/Kj4ZK1IpKL-AZF8OABp2gDqh-sA949.mp3 ;fuckgfw.com/mp3k18/a2/1429_9466.mp3  
[2013-09-06 08:57:01] 2453888 group6/M00/00/2D/Kj4ZKlIpKL6AOkQ-ACVxgMD1aRE474.mp3 ;fuckgfw.com/mp3k18/a2/1429_9460.mp3  
[2013-09-06 08:58:00] 1375232 group14/M00/00/0C/Kj4ZLVIpKPqAFmmkABT8AAoz9lU552.mp3 ;fuckgfw.com/mp3k18/a2/1487_10243.mp3  
[2013-09-06 08:58:01] 3095808 group14/M00/00/0F/Kj4ZLFIpKPqAC73LAC89ACy9iyo432.mp3 ;fuckgfw.com/mp3k18/a2/1470_10017.mp3  
[2013-09-06 08:58:01] 2378240 group14/M00/00/0F/Kj4ZLFIpKPqADabyACRKANFt20E358.mp3 ;fuckgfw.com/mp3k18/a2/1471_10021.mp3  
[2013-09-06 08:58:01] 2102144 group14/M00/00/0C/Kj4ZLVIpKPqAOF32ACATgJsIdR0090.mp3 ;fuckgfw.com/mp3k18/a2/1465_9961.mp3  

每一行中用分号开始的域是 url,且有可能会存在该 url 域相同的行,现在要做的是在一个有 13635 条记录的日志中找出这些重复的 url。

awk

接触过 shell 的童鞋可能都会马上想到用一条 awk 语句即可:

awk '{print $5}' sum_stat.log | sort | uniq  -d  

结果在意料之中:

;fuckgfw.com/mp3k18/b0/18013_209637.mp3  
;fuckgfw.com/mp3k18/b4/20059_233023.mp3  
;fuckgfw.com/mp3k18/b4/20421_237374.mp3  

如果想算出对url去重后的行数,则是:

awk '{print $5}' sum_stat.log | sort | uniq | wc -l  

结果为:

13632  

问题本身到这里其实就解决了,找到那些重复的 url。但如果仅仅这样,也没必要写篇文章记录一下了。 从上面的结果可知,虽然 url 域重复的行找出来了,但仅仅只是打印出了该域部分,而无法把整条记录打印出来。如果需要的话,该怎么做呢?

sort、uniq

上面的需求用几个纯粹的 awk 语句就可以实现,可惜对于 awk 我只会简单的 print 以及利用几个常见的内置变量如 NR、NF 来做下最基本的处理,因此暂时考虑用 sort 和 uniq 来完成。

由于平时这两个命令用的比较多,马上写出下面的语句来打印出 url 域重复的行的完整内容:

sort  -k 5,5 sum_stat.log | uniq -f 4  -d  

但是执行后,结果却是为空,没有任何东西打印出来。也就是说,用上面的语句无法找出 url 域重复的那些行。 为了再次确认,试着用下面的语句打印出 sort 和 uniq 处理后的行数:

sort  -k 5,5 sum_stat.log | uniq -f 4 | wc -l  

打印出:

13635  

如前所属,本文件一共有 13635 行,url域重复的域一共有 3 行。显然,确实上面的 sort 和 uniq 语句没能找出 url 重复的行。

罪魁祸首

反复确认脚本没有问题后,我猜可能是这些行中,有某些行的结尾有空格或 tab。用万能的 awk 找出结尾有 whtespace 的行:

awk '/[[:space:]]$/  {print NR, $0}' sum_stat.log  

结果如下:

1538 [2013-09-05 16:36:47] 2810048 group2/M00/00/0F/Kj4ZKlIos0uAfUd1ACrgwBB0ryQ704.mp3 ;fuckgfw.com/mp3k18/b0/18013_209637.mp3 
1600 [2013-09-05 16:41:47] 2119808 group24/M00/00/25/Kj4ZLVIostOAXgGtACBYgFLrM6U318.mp3 ;fuckgfw.com/mp3k18/b4/20059_233023.mp3 
1633 [2013-09-05 16:43:47] 2368640 group15/M00/00/2E/Kj4ZLFIostOAFvbKACQkgN9CyHU818.mp3 ;fuckgfw.com/mp3k18/b4/20421_237374.mp3  

可见,第1538、1600、1633这三行的结尾有多余的空白符。其实也可以猜出,这三行中的 url 就是那三个重复的 url。

把这三行的行末空白去掉后,再次运行:

sort  -k 5,5 sum_stat.log | uniq -f 4  -d -c  

结果如下:

2 [2013-09-05 16:36:47] 2810048 group2/M00/00/0F/Kj4ZKlIos0uAfUd1ACrgwBB0ryQ704.mp3 ;fuckgfw.com/mp3k18/b0/18013_209637.mp3  
2 [2013-09-05 16:41:47] 2119808 group24/M00/00/25/Kj4ZLVIostOAXgGtACBYgFLrM6U318.mp3 ;fuckgfw.com/mp3k18/b4/20059_233023.mp3  
2 [2013-09-05 16:43:47] 2368640 group15/M00/00/2E/Kj4ZLFIostOAFvbKACQkgN9CyHU818.mp3 ;fuckgfw.com/mp3k18/b4/20421_237374.mp3  

这样就顺利完成任务了。 如果打印出 url 域重复,且没去重的所有行,只需把 uniq 参数的 -d 改成 -D 即可:

sort  -k 5,5 sum_stat.log | uniq -f 4  -D  

产生如下输出:

[2013-09-05 16:36:47] 2810048 group2/M00/00/0F/Kj4ZKlIos0uAfUd1ACrgwBB0ryQ704.mp3 ;fuckgfw.com/mp3k18/b0/18013_209637.mp3  
[2013-09-06 00:35:54] 2810048 group2/M00/00/0F/Kj4ZKlIos0uAfUd1ACrgwBB0ryQ704.mp3 ;fuckgfw.com/mp3k18/b0/18013_209637.mp3  
[2013-09-05 16:41:47] 2119808 group24/M00/00/25/Kj4ZLVIostOAXgGtACBYgFLrM6U318.mp3 ;fuckgfw.com/mp3k18/b4/20059_233023.mp3  
[2013-09-06 00:33:54] 2119808 group24/M00/00/25/Kj4ZLVIostOAXgGtACBYgFLrM6U318.mp3 ;fuckgfw.com/mp3k18/b4/20059_233023.mp3  
[2013-09-05 16:43:47] 2368640 group15/M00/00/2E/Kj4ZLFIostOAFvbKACQkgN9CyHU818.mp3 ;fuckgfw.com/mp3k18/b4/20421_237374.mp3  
[2013-09-06 00:33:54] 2368640 group15/M00/00/2E/Kj4ZLFIostOAFvbKACQkgN9CyHU818.mp3 ;fuckgfw.com/mp3k18/b4/20421_237374.mp3  

这样就可以实现原来打算的功能了。

原因

为什么有行尾空格不行呢?

因为 sort 不会去除行尾空格,即使你指定了 -k 选项,而行中间的空格则不会有此问题。而 uniq 用了 -f 选项来跳过前面的不许考虑的域,同样也只能把行中间的空格略过,不会处理行尾的空格,这样导致了行尾的空格参与了比较,因而 uniq 不会把与之内容除行尾空格外均相同的行视为相同的行来去重了。