查看日志时,经常需要找某个关键字,或者从一大段输出里筛选出几行。这个时候用 grep 就很方便。它按模式匹配文本,默认输出匹配到的整行,不修改文件。

基本形式:

grep '匹配模式' 文件名

也可以放在管道后面,处理前一个命令的输出。下面用几个例子看看常用写法。

准备示例文本

把下面内容保存为 app.log,后面的命令都使用这个文件:

INFO service started
INFO request id=100 path=/api/users
WARN request id=101 path=/api/orders timeout
ERROR request id=102 connection refused
INFO retry id=102
ERROR request id=103 timeout timeout
DEBUG errorCount=2
INFO request id=104 path=/api/users

查找包含 ERROR 的行:

grep 'ERROR' app.log
ERROR request id=102 connection refused
ERROR request id=103 timeout timeout

即使匹配的只是 ERROR,输出也包含这一整行。grep 默认区分大小写,所以第 7 行的 errorCount 没有被选中。

忽略大小写,显示行号

加上 -i 忽略大小写,再加 -n 显示行号:

grep -in 'error' app.log
4:ERROR request id=102 connection refused
6:ERROR request id=103 timeout timeout
7:DEBUG errorCount=2

这时 errorCount 也匹配到了,因为 grep 默认不要求匹配整个单词。

如果只想找独立的 error,可以使用 -w:

grep -inw 'error' app.log
4:ERROR request id=102 connection refused
6:ERROR request id=103 timeout timeout

-w 判断的是单词边界,字母、数字和下划线都算单词字符。比如 error_count 中的 error 也不会单独匹配。

排除某些行

-v 反向选择,输出不匹配的行。去掉以 INFO 开头的记录:

grep -v '^INFO ' app.log
WARN request id=101 path=/api/orders timeout
ERROR request id=102 connection refused
ERROR request id=103 timeout timeout
DEBUG errorCount=2

^ 表示行首,$ 表示行尾。比如 ^INFO service started$ 就要求这一整行完全匹配。

也可以通过管道组合条件,找出同时包含 ERROR 和 timeout 的行:

grep 'ERROR' app.log | grep 'timeout'
ERROR request id=103 timeout timeout

匹配多个模式

想同时查看 WARN 和 ERROR,可以用多个 -e。任意一个模式匹配,这一行就会输出:

grep -e '^WARN ' -e '^ERROR ' app.log
WARN request id=101 path=/api/orders timeout
ERROR request id=102 connection refused
ERROR request id=103 timeout timeout

使用 -E 开启扩展正则表达式,也可以写成:

grep -E '^(WARN|ERROR) ' app.log

结果相同。这里括号把两个分支放在一起,| 表示“或者”,行首限制对两个分支都生效。

按普通字符串查找

grep 默认把模式当成正则表达式。查找带有点号、方括号等字符的内容时,如果只想按原样匹配,可以加 -F:

printf 'server=api.example.com\nserver=apiXexampleYcom\n' | grep -F 'api.example.com'
server=api.example.com

如果去掉 -F,正则里的点号可以匹配任意单个字符,第二行也会被选中。

看一下前后文

只有报错那一行,有时还看不出发生了什么。-B 显示匹配行之前的几行,-A 显示之后的几行,-C 则同时显示前后文。

查找连接失败的记录,连同前后各一行一起输出:

grep -n -C 1 'connection refused' app.log
3-WARN request id=101 path=/api/orders timeout
4:ERROR request id=102 connection refused
5-INFO retry id=102

这里匹配行的行号后是冒号,上下文行的行号后是连字符。

统计行数和匹配次数

-c 统计的是匹配的行数:

grep -c 'timeout' app.log
2

第 6 行虽然出现了两次 timeout,仍然只算一行。

如果要统计这个字符串出现了几次,可以用 -o 只输出匹配到的部分,每次非空匹配单独占一行:

grep -o 'timeout' app.log
timeout
timeout
timeout

再接上 wc -l:

grep -o 'timeout' app.log | wc -l

结果是 3。这里统计的是不重叠的匹配次数,不能把 -c 直接当成出现次数。

在目录中查找

日志分散在多个文件里时,可以递归查找。下面只检查 logs 目录下文件名以 .log 结尾的文件:

grep -rn --include='*.log' 'ERROR' logs/

输出会带上文件名和行号。如果只想知道哪些文件包含 ERROR,不需要匹配内容,把 -n 换成 -l:

grep -rl --include='*.log' 'ERROR' logs/

-l 输出的是文件名,每个匹配文件只列一次;--include 后面的通配符加上引号,交给 grep 处理。

在脚本中判断

只关心有没有匹配时,用 -q,不输出内容:

if grep -q 'ERROR' app.log; then
    echo '发现 ERROR 日志'
fi

输出:

发现 ERROR 日志

grep 通常以状态码 0 表示匹配到,1 表示没有匹配,2 表示执行出错。脚本里的 if 判断的是状态码,不是屏幕上有没有输出。

最后修改:2017 年 04 月 24 日
如果觉得我的文章对你有用,请随意赞赏