2012年3月29日星期四

几个简单常用的Linux命令——日志分析篇


同事说能否分享一下linux日志分析的心得,尽管我也只是初学小菜,不过既然问到了,那就献丑了。

linux里面有那么几个常用的命令,在大部分日志分析场景里面都会用到:

cut  按照特定分割符分成若干列,取出其中的某一列。
     -d" " 表示用空格作为分割符默认好像是逗号;
     -f8   表示取出第8列。

sort 按照特定的分隔符分成若干列,按照某一列进行排序。
     -d" " 表示用空格(默认应该就是空格),-f1,2表示用第1、2列输出;
     -g    表示按照数字进行排序;
     -r    表示逆序;(默认从小到大);
     -k8   表示根据第8个字段进行排序;(默认只根据第1列排序,可以通过设置IFS来指定分隔符)
     -k1,3 表示根据第1至3个字段之间的内容进行排序;
     -u    表示相同的元素只输出一次。

uniq 如果下一行重复则消除。
     -c    表示统计出现次数(重复一次计数为2)。

head 输出最开始的几行。
     -n8   表示输出头8行。(无参数时默认10行)

tail 输出最末尾的几行。
     -n8   表示输出尾8行。(无参数时默认10行)

wc   输出指定文字出现的次数。
     -l    表示输出出现该文字的行数而不是次数。

grep 输出包含指定文字的若干行。
     -i    表示大小写不敏感;
     -e    表示使用正则表达式;(等同于egrep命令)
     -v    表示输出不等于制定文字的若干行;
     -m8   表示指定文件中的输出前8个;
     -a    表示如果是二进制文件,则仅对比里面是文字的部分;(比如要找squid缓存文件当中记录的,与该文件对应的uri时就特别有用);
     -r    表示递归搜索所有文件夹中的指定文件;
     -H    在每一行输出中均包括文件名;(搜索单个文件时默认不输出)
     -h    在每一行输出中均不包含文件名;(搜索多个文件时默认输出)
     -l    仅输出有命中的文件名;
     -L    仅输出没有命中的文件名;
     -B8   输出命中该行之前的8行;
     -A8   输出命中该行之后的8行;
     -c    仅输出命中次数。

上面这个命令组合通常就可以进行大部分的日志分析工作,下面举例说明。

我们假设日志名称为squid.log,格式如下所示(有点像Squid,但实际上不是真实的日志格式,只是为了便于说明):
- - [2012/1/1 04:23:44] 223.33.54.123 "GET /pages/head.htm HTTP/1.1" {www.fix.com|http://www.google.com/search} 200 - - TCP_HIT:NONE "Mozilla/4.0"

例1 查看最后若干个访问"/pages/head.htm"的IP都来自哪里:
grep -i "/pages/head.htm" squid.log | tail -n20 | cut -d" " -f5
注意:

  1. 日期和时间当中有一个空格,因此是两个字段,而不会因为方括号而认为是一个字段。同样的,单引号双引号也不会影响以空格为分割符的分割;
  2. 如果文件非常大导致明显非常慢,可以尝试用tail作为开头进一步限制,如:

tail -n1000 squid.log | grep -i "/pages/head.htm" | tail -n20 | cut -d" " -f5



例2 统计最后若干个访问"/pages/head.htm"的IP的次数:
grep -i "/pages/head.htm" squid.log | tail -n1000 | cut -d" " -f5 | sort | uniq -c | sort -g
备注:这里的技巧是,先要sort,然后再通过uniq -c来统计次数,然后再通过sort来进行排序。这是因为对于uniq命令来说,之比较上一行和下一行,而不会考虑交错的情况,例如对于文件tmp如下:
A
A
B
B
A
A
C

用uniq -c tmp来检查,输出如下:
2 A
2 B
2 A
1 C

而实际上我们希望的输出类似如下:
1 B
2 C
4 A

于是只能够通过sort先将相同的内容放到一起,然后再用uniq来统计。sort命令当中的-u参数并不会统计处数量,于是必须使用uniq命令。同时uniq输出的顺序是原始文本出现的顺序,而不是根据数量进行排序的,因此最后还需要用sort进行排序。


例3 统计最后若干个访问"/pages/head.htm"的IP中,出现次数最多的前三名以及其次数:
grep -i "/pages/head.htm" squid.log | tail -n1000 | cut -d" " -f5 | sort | uniq -c | sort -gr | head -n3



例4 统计后端返回状态为503的访问次数:
cut -d" " -f10 "/pages/head.htm" squid.log | grep 503 | wc -l


例5 统计后端返回状态为200,且访问"/pages/head.htm"的HIT和MISS的情况(日志中TCP_HIT:NONE的那一列)
这一个需求比较复杂,我们需要动用上面没有介绍到的另一个命令:awk

awk  对每一行按照指定分隔符进行分割,并根据指定的条件输出指定的内容。
     -F" " 表示输出以空格作为分割符。

完整命令格式如下:

awk [条件] ['指令'] [待分析文件]
之所以指令是可选的,是因为参数中还有一个可以指定指令文件的参数;而待分析文件可选,是因为可以通过管道提供待分析文件。

其中的指令部分比较复杂,完整的这里就不介绍了,请自行谷歌之。这里介绍非常简单的几个常用部分。
$1       表示第一个字段
$1>2     表示第一个字段是数字且大于数字2
$1>"2"   表示第一个字段大于字符2,也就是说"12"<"2"
$1==$2   表示第一个字段等于第二个字段
&&       表示与
||       表示或
a { b }  表示如果前面的a部分匹配,则执行b部分
print    表示输出整行
print $1 表示输出第一个字段
;        表示两个指令之间的分割

于是
$1==200 { print; }
就是说第一列等于200就输出整行。

对于本示例,将需要使用如下命令:
awk -F" " '$10==200 { print; }' squid.log | grep -i "/pages/head.htm" | cut -d" " -f13 | sort | uniq -c | sort -g


例5 统计UserAgent的情况,但前面的访问路径处可能包含不定数量的空格,比如某些访问没有发出HTTP/1.1,于是只有"GET /pages/head.htm"。


这个问题很容易想到:啊,如果能够忽略双引号中的空格多好啊!嗯,这种思路应该也是可以的,但是比起下面这种思路来说,有的时候真的太复杂了。
cut -d\" -f4 squid.log | sort | uniq -c | sort -g

“……靠,还能用双引号做分割符,咋就没想到呢?”好吧,再提示一下:每一个命令可以考虑使用不同的分割符,而不需要每一次都用同一个分割符。

没有评论:

发表评论