使⽤tail结合grep查⽇志关键字并⾼亮及显⽰所在⾏上下⽂
此⽂转载⾃:my.oschina/goldenshaw/blog/4703734
对于⼀个开发或运维⼈员⽽⾔, 当系统出现故障时, 第⼀步常常就是查看⽇志. 查看⽇志经常碰到的⼀个需求就是按关键字去搜索, 在⽇常开发机⼦上的 IDE 上, 都集成了强⼤的搜索功能, 但因为系统通常部署在 Linux 系统上, ⼀般只有命令⾏界⾯, 在其上应该怎么去搜索呢? 恐怕有些同学就不是那么清楚了.
有些⼈会⽤ ftp 之类的把⽇志下载下来本地再搜索, 如果是⼩⼀点的⽂件还好, 但⽇志⽂件往往都⽐较⼤, 因此这样的⽅式⽆疑是极为低效的.
下⾯就介绍⼀种相对快捷的⽅式, 也不需要⽤到特别⾼级的命令, 仅需要 tail 和 grep 两个命令结合起来即可, 能达到这样⼀个效果:
能按关键字搜索;
在显⽰关键字所在⾏时还能⾼亮关键字;
能把关键字所在⾏的上下⽂, ⽐如上下 10 ⾏的内容也⼀起显⽰出来.
下⾯是⼀个效果⽰意图:
在这⾥, 我⽤我云主机的 nginx access log 做了个⽰范, 我搜索⼀篇⽂章 url 的关键字 "a-port", 然后显⽰出搜索的结果及上下⽂, 可以看到关键字被标红显⽰, 上下⽂也有显⽰, 多个搜索结果间以蓝⾊的短横间隔开来.
下⾯具体说说怎么实现这样的搜索, 先具体讲讲各个命令及参数, 再说说怎么结合起来, 最后还给出⼀个脚本化的⾼级⽤法.
tail 命令
⾸先是 tail 命令. 因为查看⽇志通常从后⾯最新的⽇志去看, tail 命令就是从后往前.
⽐如下述命令会显⽰ access.log 的最后 10 ⾏的内容:
tail access.log
tail 指定⾏数
默认情况下, tail 只会显⽰最后的 10 ⾏, 对于⼀个⽇志很多的应⽤来说, 这可能是不够的, 为此我们需要搜索更多的⾏.
如果想实时查看⽇志, 可以参考之前的这篇⽂章
tail 可以结合 -n 参数指定⼀个⾏数, ⽐如下述命令会显⽰最后的 30 ⾏的⽇志:
tail -n 30 access.log
注: 如果不太能记住参数, 还可以使⽤ -n 的完整命令参数 --lines:
tail --lines 30 access.log
grep 命令
tail 仅能打印显⽰⽇志, 很多时候这是不够的, ⽇志通常⾮常多, ⽽且很多是没有⽤, 我们还需要能过滤, 或者说搜索筛选⽇志的内容, 这时就可以使⽤ grep 命令.
grep 命令的基本⽤法是这样的. 假如你有⼀个⽂件 index.html, 你想在其中搜索⼀个关键词 official, 你可以这样⽤:
grep official index.html
结果如下:
它会把关键字所在⾏给你显⽰出来, 并⾼亮关键字.
关于⾼亮问题, 如果缺省没有⾼亮, 则可以⾃⾏加⼊ --color 选项, 像这样: grep --color official index.html
注意, 通常不要直接⽤ grep 命令去搜索整个⽇志⽂件, 因为⽇志⽂件通常很⼤, ⽽且 grep 也是从开头开始搜索的, 因此可能搜索出⼀⼤堆你不感兴趣的历史记录.
后⾯将介绍如何结合 tail 和 grep 命令以缩⼩搜索范围.
带有空格的关键字
如果关键字有多个单词并带有空格, 可以使⽤ '' 单引号引起来, 例如:
grep 'english version' index.html
grep 显⽰上下⽂
有时, 我们不但要出关键⼦所在⾏, ⽽且还想显⽰所在⾏上下的⼀些⾏.
这在查异常信息时⾮常常见, ⼀⽅⾯异常栈会打印成⾮常多⾏, 另外我们通常需要前前后后都看⼀下到底发⽣了什么.
这时可以使⽤ grep 的 -NUM 参数来实现, 如下:
grep -5 official index.html
或者是使⽤ -C
grep -C 5 official index.html
它表⽰, 不但要出 official 关键字所在⾏, 还要把所在⾏前后的 5 ⾏都显⽰出来.
后接的数字 5 就表⽰前后 5 ⾏, 如果是 -10 就表⽰前后 10 ⾏
结果如下:
grep 显⽰⾏号
为了更清晰地呈现, 还可以选择显⽰⾏号, ⽤ -n 参数, 如下:
grep -5 -n official index.html
结果如下图:
可以看到, official 关键字在 21 ⾏, ⾏头的⾏号还特别以冒号":" 标出; 此外, 关键⾏的前 5 ⾏(16~20)和后 5 ⾏(22~26)也⼀并显⽰了出来.
如果我们是在跟踪⼀个异常, 这些上下⽂的信息可能会提供很多帮助.
管道符的使⽤
现在已经介绍完了 tail 和 grep 命令, 但还有⼀个问题, 如果直接在⽇志⽂件中去 grep 的话, 因为⽂件通常特别⼤, ⽽且很多历史数据可能不是我们想要的, 因此最好的⽅式是先⽤ tail 得到后⾯的那些⾏, 然后把 tail 出来的结果再交给 grep 命令去过滤, ⽽管道符可以实现这个⽬的, 管道符在命令⾏中就是⼀个"竖杠": |.
它可以把两个命令结合起来, 把请⼀个命令的输出当作后⼀个命令的输⼊.
⽤管道符 | 结合 tail 和 grep 命令
⽤管道符结合 tail 和 grep 命令可以这样去写:
tail error.log | grep stream
注意: grep 之前的竖杠 |.
上述命令会把 tail 出来的最后 10 ⾏的内容交给 grep 去搜索过滤, 并出其中含有 stream 关键字的⾏, 结果如下:
结合前⾯所讲, 如果想在更⼤范围搜索并显⽰关键字的上下⽂, 最终可以这样去写:
tail -n 20 error.log | grep -3 stream
以上命令在最后 20 ⾏中去搜索 stream 关键字并显⽰关键字所在⾏及上下各 3 ⾏的内容, 结果如下:
⾼级⽤法
有了以上命令, 要搜索异常信息就简单了不少, ⽽且更容易观察, 不过还是有⼀个问题, 就是整个命令还是
太长了些, 如果想进⼀步简化, 则可以考虑将整个命令做成⼀个脚本, 并将部分参数值参数化, 这就带有⼀定的编程的味道了, 好在这对于我们程序员来说, 不算太难的事, 甚⾄是我们的⽇常, 下⾯说说怎么去实现.
先说下效果, 我们会编写⼀个脚本叫 search.sh
当然这个名字你可以⾃⼰去取
然后这样去⽤:
./search.sh stream 20 3
然后其效果就像执⾏下述命令⼀样:
tail -n 20 error.log | grep -3 stream
如果不打算传⼊⾏数及上下⽂的数⽬, ⽽使⽤脚本中定义的缺省值, 整个命令还可以简化成:
./search.sh stream
仅需要传⼊要搜索的关键字即可, 其它参数保持缺省.
⾃定义命令
就以搜索我本机上的 nginx 的 error.log 为例吧, ⾸先创建⼀个脚本⽂件 search.sh
touch search.sh
⽂件的内容如下:
#!/bin/bash
cd /usr/local/nginx/logs
tail -n 20 error.log | grep --color -3 stream
注意: 放⼊脚本⽂件时, 如果没有⾼亮, 需要⾃⾏加上 --color 选项
逻辑也⽐较简单, 就是先进⼊ error.log 所在⽂件夹, 然后执⾏查.
有了 cd 命令, 就可以直接把脚本放在远程登录后的⽤户⽬录下, ⽐如 /root 下, 这样进去了就可以直接执⾏, 连进⼊⽂件夹的动作也省略了.
另外, 如果不想⽤ cd 命令, 也可以在 tail 中写上完整路径名.
当然, 现在脚本还是⽐较死的, 搜索的关键字被写死了. 不过⽬前来说, 我们先测试其它⽅⾯, 先把⽂件改成可执⾏的:
chmod 755 search.sh
然后可以先执⾏⼀遍看看是否 ok, 如果 ok 了, 再下⼀步准备把关键字参数化.
./search.sh
参数传递
现在需要把搜索的关键字给参数化, 不然执⾏脚本时, 始终只能搜索 'stream' 这个关键字, 这显然不是我们希望的.
如果是⽤我们熟悉的语⾔, ⽐如 java, javascript, 写⼀个可以接收参数的函数是很简单的, 其实对于 bash 这种脚本语⾔来说, 主要的问题是我们不熟悉其语法, 这个只要稍微查下它的⼿册或是在⽹上搜索下即不难知道.
过程就不提了, 具体⽽⾔是这样的:
#!/bin/bash
cd /usr/local/nginx/logs
tail -n 20 error.log | grep --color -3 $1
就是把 stream 这个写死的关键字变成⼀个变量 $1, ⾃然 $ 符号就是 bash 跟定义变量有关的.
⾃然, 你应该能猜到, 如果想传递更多的参数, 就⽤ $2, $3, 以此类推.
然后你这样
./search.sh hello
那么脚本⽂件名后⾯跟的字符串'hello'就会传递给 $1 这个变量, 于是就相当于执⾏了:
tail -n 20 error.log | grep --color -3 hello
同理, 可以把 tail 的⾏数和 grep 的上下⽂的⾏数也参数化:
#!/bin/bash
cd /usr/local/nginx/logs
tail -n $2 error.log | grep --color -$3 $1
如此⼀来, 当执⾏下述命令时:
./search.sh hello 1000 10
就相当于:
tail -n 1000 error.log | grep --color -10 hello
也即在⽇志⽂件的最后 1000 ⾏⾥搜索, 并显⽰关键⾏上下各 10 ⾏的内容.
缺省值及判断逻辑
⾃然, 很多时候可能只想传递关键字即可, 当把 tail 的⾏数和 grep 的上下⽂的⾏数也参数化后, 每次调⽤也要传递它们是不⽅便的, 当如果把它们写死的话, 有时我们可能⼜需要适当变化, 这个⽭盾怎么解决呢? 答案是利⽤缺省值和逻辑判断.
如果是常⽤的语⾔, 如 java, javascript, 写个这种判断相信对你来说是个再简单不过的事, 对于 bash 这种脚本语⾔, 最⼤的问题还是我们不熟悉其语法, 那么这个还是跟之前说的那样, 查查⼿册, 或搜索下, 过程就省略了, 具体来说, 可以这样:
#!/bin/bash
lineCount=1000
if [ $2 ]; then
lineCount=$2
fi
contextCount=10
if [ $3 ]; then
contextCount=$3
fi
cd /usr/local/nginx/logs
tail -n $lineCount error.log | grep --color -$contextCount $1
简单说就是定义两个变量lineCount和contextCount, 分别具有 1000 和 10 两个缺省值, 然后利⽤ if 判断⽤户是否输⼊了第⼆和第三个参数, 如果有, 就⽤它们的值取代缺省值, 没有的话就使⽤缺省值, 这样⼀来就⽐较灵活了.grep命令有什么用
如果只输⼊了关键字:
./search.sh hi
结果就是这样:
tail -n 1000 error.log | grep --color -10 hi
输⼊两个参数:
./search.sh hello 300
结果就是这样:
tail -n 300 error.log | grep --color -10 hello
输⼊三个参数:
./search.sh hey 500 8
结果就是这样:
tail -n 500 error.log | grep --color -8 hey
当然还是有个问题, 当你想只调整第三个参数时, 你还是必须得传⼊第⼆个参数, 否则传⼊的值只会被第⼆个参数优先获得.
命令输出
最后, 如果你想在执⾏前回显⼀下将要执⾏的命令, 还可以利⽤ echo 这个命令来实现, 它同样⽀持变量:
#!/bin/bash
lineCount=1000
if [ $2 ]; then
lineCount=$2
fi
contextCount=10
if [ $3 ]; then
contextCount=$3
fi
cd /usr/local/nginx/logs
echo "========= tail -n $lineCount error.log | grep --color -$contextCount $1"
tail -n $lineCount error.log | grep --color -$contextCount $1
这样⼀来, 执⾏前就会先打印出将要执⾏的命令.
总结
综上所述, 从单个命令到复合命令, 再到脚本化和参数化, 其实是⽤了编程中的抽象这⼀⼿法, 这是我们解决重复性以及解决复杂性的⼀种重要⼿段.
当⼀个命令或⼏个的复合命令⽐较繁琐时, 我们就⽤⼀个脚本⽂件去做抽象, 保留不变的东西, 把变化的东西参数化, 外部化, 通过这样的⽅式, 就简化了执⾏(调⽤)的过程, 减少了重复.
毕竟, 如果你经常需要查⽇志的话, 输⼊简单的 ./search.sh foo ⽐反复输⼊如此之长的⼀串 tail -n 1000 error.log | grep --color -10 foo 要⽅便快捷的多.
作为⼀名程序员, 减少重复是我们的天职, 我们应该是怕重复, 怕⿇烦的, 某种意义上, 我们应该是"懒惰"的:
还记得 Perl 语⾔的发明⼈ Larry Wall 的话吗: "优秀程序员应该有三⼤美德:懒惰、急躁和傲慢(laziness, impatience and hubris)"
更多关于抽象及重复的话题, 可以参考之前专题, 关于使⽤ tail 结合 grep 查⽇志关键字并⾼亮及显⽰所在⾏上下⽂就介绍到这⾥.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论