数据分析的七种武器-shell
原文链接 http://woodrat.xyz/2018/09/10/seven-weapons-of-data-analysis-shell/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
假设我们现在有这样一个任务,需要快速从 Nignx logs 中统计出访问量前10的 ip 及其访问次数。
以 github 上的nginx_logs 为例 (格式参考官方文档)
首先我们可以使用 head 命令取出该日志的前10条,观察其格式。
root:Desktop# head -n 10 nginx_logs.txt
93.180.71.3 - - [17/May/2015:08:05:32 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
93.180.71.3 - - [17/May/2015:08:05:23 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
80.91.33.133 - - [17/May/2015:08:05:24 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"
217.168.17.5 - - [17/May/2015:08:05:34 +0000] "GET /downloads/product_1 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
217.168.17.5 - - [17/May/2015:08:05:09 +0000] "GET /downloads/product_2 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
93.180.71.3 - - [17/May/2015:08:05:57 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
217.168.17.5 - - [17/May/2015:08:05:02 +0000] "GET /downloads/product_2 HTTP/1.1" 404 337 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
217.168.17.5 - - [17/May/2015:08:05:42 +0000] "GET /downloads/product_1 HTTP/1.1" 404 332 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
80.91.33.133 - - [17/May/2015:08:05:01 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"
93.180.71.3 - - [17/May/2015:08:05:27 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
awk
可以看到,访问的源 IP 是处于每行日志的第一列,并且和其他日志内容是以空格隔开的。处理这种场景刚好可以用上 awk 。 awk 是一个复杂的工具,我们只需要使用分割字符串和执行 print 或者 printf 进行格式化的功能。在这里为 awk -F ' ' '{print $1}'
,其中 -F
用来指定分割符,单引号中是 awk 的表达式。
关于 awk 更复杂的使用可以参考陈浩的博客AWK 简明教程
root:Desktop# head -n 10 nginx_logs.txt |awk -F ' ' '{print $1}'
93.180.71.3
93.180.71.3
80.91.33.133
217.168.17.5
217.168.17.5
93.180.71.3
217.168.17.5
217.168.17.5
80.91.33.133
93.180.71.3
使用 awk 提取出第一列的 IP 后,我们需要使用 sort 和 uniq 将相同的 ip 合并并且统计个数。在 Shell 中,我们通常使用 |
管道符号将上一个命令的输出作为下一个命令的输入。先使用 sort 将相同的 ip 排列到一起,再使用 uniq -c 合并相同的 ip 并计数。
root:Desktop# head -n 10 nginx_logs.txt |awk -F ' ' '{print $1}' | sort |uniq -c
4 217.168.17.5
2 80.91.33.133
4 93.180.71.3
得到了每个 ip 的计数后,使用 sort -nr
即可按每个 ip 计算降序排列。其中 -n 是按照数字顺序排序,-r 是降序排序。
root:Desktop# head -n 10 nginx_logs.txt |awk -F ' ' '{print $1}' | sort |uniq -c |sort -nr
4 93.180.71.3
4 217.168.17.5
2 80.91.33.133
有了上面的过程,我们只需要把用来验证正确性的 head -n 10
替换为 cat
,并再最后加上 head -n 10
限制只输出前10,即可得到最后的结果。
root:Desktop# cat nginx_logs.txt |awk -F ' ' '{print $1}' | sort |uniq -c |sort -nr | head -n 10
2350 216.46.173.126
1720 180.179.174.219
1439 204.77.168.241
1365 65.39.197.164
1202 80.91.33.133
1120 84.208.15.12
1084 74.125.60.158
1064 119.252.76.162
628 79.136.114.202
532 54.207.57.55
grep
当我们有多个日志文件时,就需要用到 grep
去搜索哪个文件中有我们需要的内容。
例如有多天的 nginx access log: nginx_logs.txt.1 nginx_logs.txt.2 nginx_logs.txt.3
,我们需要找到有对应 /admin.php
后台管理页面访问的日志,
可以使用 grep -Rl "/admin.php" .
在当前目录递归搜索包含 /admin.php
字符串的文件名。
如果需要统计某天的 nginx 日志中 status code 为 404 的数量,这时使用 awk 不太容易分离出 status code 字段,我们可以使用 grep 来提取对应的字段。
单行日志格式如下:
93.180.71.3 - - [17/May/2015:08:05:32 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
对应的 grep 命令如下:
root:Desktop# head -n 10 nginx_logs.txt |grep -Po "\".*\"\K \d{3}"
304
304
304
200
200
304
404
404
304
304
其中 -P
指定了使用 pcre 语法,-o
指定只输出match的部分。
因为日志内容中有多个地方都有连续 3 位的数字,仅仅使用 \d{3}
无法正确匹配到 status code。需要先识别到前面的 request line(关于 HTTP 协议字段可以参考我以前的文章)。
使用ignore-pattern\K
的语法可以指定前面需要匹配的串并忽略,然后只提取后面的 pattern。(某些情况下也可以用 (?>=pattern)
来忽略前面的串,具体参考 StackOverflow )
最后使用 wc count 一下就能得到结果了
root:Desktop# cat nginx_logs.txt |grep -Po "\".*\"\K \d{3}" |grep 404 |wc -l
33876
sed
sed 一般用于将文件中正则搜索到的字符串替换为另外一个字符串。例如,在一个文件夹中将 std::cout 替换为 LOG(INFO)
sed -i "s/std::cout/LOG(INFO)/g" `grep -rl "std::cout" .
Bash Script
参考阅读:
Shell 环境 http://www.tldp.org/LDP/Bash-Beginners-Guide/html/
Bash Script 编写 https://mywiki.wooledge.org/BashGuide/