awk 可以把一行文本拆成多个字段,再按字段筛选、计算或输出。平时查看日志,想取出 IP、访问时间这些内容,用 awk 就很方便。

默认情况下,awk 按换行读取记录,按连续的空格或 Tab 拆分字段。可以直接读取文件,也可以通过管道处理前一个命令的输出。基本形式是:

awk '条件 {动作}' 文件名

省略条件,动作就对每条记录执行;省略动作,则输出符合条件的整条记录。

从日志中取字段

以 Nginx 访问日志为例,把下面五行示例保存为 access.log:

192.0.2.10 - - [21/Jul/2017:14:22:48 +0800] "GET / HTTP/1.1" 200 2311 "-" "curl/7.29.0"
192.0.2.10 - - [21/Jul/2017:14:22:49 +0800] "GET /css/global.css HTTP/1.1" 200 1033 "-" "curl/7.29.0"
192.0.2.20 - - [21/Jul/2017:14:25:45 +0800] "GET /missing HTTP/1.1" 404 162 "-" "curl/7.29.0"
192.0.2.30 - - [21/Jul/2017:14:26:29 +0800] "GET / HTTP/1.1" 200 2311 "-" "curl/7.29.0"
192.0.2.40 - - [21/Jul/2017:14:26:30 +0800] "GET /favicon.ico HTTP/1.1" 404 162 "-" "curl/7.29.0"

取最后五行,输出 IP 和访问时间:

tail -n 5 access.log | awk '{print $1 "\t" $4}'

结果为:

192.0.2.10    [21/Jul/2017:14:22:48
192.0.2.10    [21/Jul/2017:14:22:49
192.0.2.20    [21/Jul/2017:14:25:45
192.0.2.30    [21/Jul/2017:14:26:29
192.0.2.40    [21/Jul/2017:14:26:30

$1、$4 分别表示第一个和第四个字段,$0 表示整条记录。这里 $4 带着左方括号,时区则在 $5 中。

只看现有的最后五行,用 tail -n 5 即可;需要持续查看新增日志时,再用 tail -n 5 -f。后面的示例都处理固定内容,方便对照结果。

加上筛选条件

只输出 IP 为 192.0.2.10 的记录,在动作前加上条件:

tail -n 5 access.log | awk '$1 == "192.0.2.10" {print $1 "\t" $4}'

结果为:

192.0.2.10    [21/Jul/2017:14:22:48
192.0.2.10    [21/Jul/2017:14:22:49

如果想看这些请求的完整日志,就省略后面的动作:

awk '$1 == "192.0.2.10"' access.log

这里输出的就是示例文件的前两行。

NR、NF 和 FS

awk 有几个常用的内置变量:

变量含义
NR已读取的记录总数;默认按行读取时,就是当前读到的行数
NF当前记录的字段数
FS输入字段分隔符

把行号和字段数一起打印出来:

tail -n 5 access.log | awk '{print $1 "\trow:" NR "\tcolumns:" NF}'

结果为:

192.0.2.10    row:1    columns:12
192.0.2.10    row:2    columns:12
192.0.2.20    row:3    columns:12
192.0.2.30    row:4    columns:12
192.0.2.40    row:5    columns:12

这里的 NR 从管道传入的第一行开始计数,不是这条日志在原文件中的行号。

awk 默认不会把引号中的内容当成一个整体。比如日志里的 "GET / HTTP/1.1" 仍然占三个字段;User-Agent 中如果有空格,也会继续拆分,所以真实日志的 NF 不一定相同。

在 BEGIN 中设置分隔符

/etc/passwd 使用冒号分隔字段,第一个字段是账号名,第三个字段是 UID。为了让结果固定,下面用一个示例文件 users.txt:

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
app:x:1000:1000:app:/home/app:/bin/bash
worker:x:1001:1001:worker:/home/worker:/bin/bash

设置 FS 时,下面这种写法有个问题:

awk '{FS=":"} {print $1 "\t" $3}' users.txt

普通动作在读入记录之后才执行。第一行读入时还是默认分隔符,此时再修改 FS,不会按新分隔符重新拆分这一行。

把初始化放到 BEGIN 中,就能在读取第一行之前设置好:

awk 'BEGIN {FS=":"} {print $1 "\t" $3}' users.txt

结果为:

root    0
bin    1
daemon    2
app    1000
worker    1001

指定分隔符也可以直接用 -F,写起来更短:

awk -F ':' '{print $1 "\t" $3}' users.txt

在 END 中汇总

BEGIN 在读取数据之前执行,END 在输入结束后执行。统计 UID 大于 20 的记录数:

awk 'BEGIN {FS=":"; count=0} $3>20 {count++} END {printf "total is : %d\n", count}' users.txt

结果为:

total is : 2

BEGIN 中的两条赋值语句用分号分隔。每读到一条符合条件的记录,count 加一,最后通过 printf 输出整数和换行。

直接统计本机账号时,把 users.txt 换成 /etc/passwd 即可,数量以本机数据为准。END 要等输入结束才执行,所以这种一次性统计不要接在持续等待新日志的 tail -f 后面。

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