您现在的位置是:网站首页>技术百科技术百科
AWK 使用教程
小大寒2024-01-01[技术百科]博学多闻
AWK 使用教程本文介绍了使用`awk`命令对`netstat`命令输出进行处理的基本方法。首先展示了如何提取特定列的数据,然后通过格式化输出使得结果更加整齐。接着,介绍了如何通过条件过滤记录,例如仅输出状态为`LISTEN`的行。文中还讲解了`awk`的内建变量,如`$0`(当前行)、`$1~$n`(字段)、`NR`(记录数)等,并举例说明了它们的实际应用。
AWK 使用教程
开始
以下是从 netstat
命令中提取的部分信息,用作示例:
$ cat netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 xinxiyishu.com:80 192.168.0.99:10099 TIME_WAIT tcp 0 0 xinxiyishu.com:80 151.0.113.45:21345 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 198.51.88.25:1032 ESTABLISHED tcp 0 0 xinxiyishu.com:80 151.0.113.50:56231 ESTABLISHED tcp 0 0 xinxiyishu.com:80 198.51.88.50:16802 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 151.0.113.50:32167 ESTABLISHED tcp 0 0 xinxiyishu.com:80 192.0.2.30:36970 TIME_WAIT tcp 0 4166 xinxiyishu.com:80 198.51.88.60:30901 ESTABLISHED tcp 0 1 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 tcp 0 0 xinxiyishu.com:80 198.51.88.25:4796 ESTABLISHED tcp 0 0 xinxiyishu.com:80 192.0.2.40:51082 TIME_WAIT tcp 0 1 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK tcp 0 0 xinxiyishu.com:80 151.0.113.50:78352 ESTABLISHED tcp 0 0 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2 tcp 0 0 :::22 :::* LISTEN
下面展示一个最基本、最常用的 awk
示例,输出第一列和第四列:
- 在单引号中的部分为
awk
的语句,注意,awk
语句只能被单引号包裹。 - 其中的
$1
到$n
表示输出的列号。需要注意的是,$0
代表整行内容。
$ awk 'print $1, $4' netstat.txt Proto Local-Address tcp 0.0.0.0:3306 tcp 0.0.0.0:80 tcp 127.0.0.1:9000 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp xinxiyishu.com:80 tcp :::22
接下来,我们来看看awk命令的格式化输出,这与C语言中的printf函数类似:
$ awk '{printf "%-8s %-8s %-8s %-18s %-22s %-15s\n",$1,$2,$3,$4,$5,$6}' netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 xinxiyishu.com:80 233.210.5.146:10099 TIME_WAIT tcp 0 0 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 66.194.134.189:1032 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:56231 ESTABLISHED tcp 0 0 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 219.169.233.111:32167 ESTABLISHED tcp 0 0 xinxiyishu.com:80 76.60.215.36:36970 TIME_WAIT tcp 0 4166 xinxiyishu.com:80 155.148.242.38:30901 ESTABLISHED tcp 0 1 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 tcp 0 0 xinxiyishu.com:80 66.194.134.189:4796 ESTABLISHED tcp 0 0 xinxiyishu.com:80 76.60.212.163:51082 TIME_WAIT tcp 0 1 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK tcp 0 0 xinxiyishu.com:80 219.169.233.111:78352 ESTABLISHED tcp 0 0 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2 tcp 0 0 :::22 :::* LISTEN
进一步
过滤记录
接下来,我们看看如何通过awk过滤记录,以下例子展示了过滤条件:第三列的值为0且第六列的值为LISTEN。
$ awk '$3==0 && $6=="LISTEN" ' netstat.txt tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN
在awk中,“==”是用来进行比较的运算符。其他的比较运算符包括:!=、>、<、>= 和 <=。
接下来,我们再看一些常见的记录过滤方式:
$ awk ' $3>0 {print $0}' netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 4166 xinxiyishu.com:80 155.148.242.38:30901 ESTABLISHED tcp 0 1 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 tcp 0 1 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK
如果我们希望在输出结果中包含表头,可以通过引入内建变量NR来实现:
$ awk '$3==0 && $6=="LISTEN" || NR==1 ' netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN
我们可以加上格式化输出,使其更加整齐:
$ awk '$3==0 && $6=="LISTEN" || NR==1 {printf "%-20s %-20s %s\n",$4,$5,$6}' netstat.txt Local-Address Foreign-Address State 0.0.0.0:3306 0.0.0.0:* LISTEN 0.0.0.0:80 0.0.0.0:* LISTEN 127.0.0.1:9000 0.0.0.0:* LISTEN :::22 :::* LISTEN
内建变量
接下来,我们来介绍一些awk的内建变量:
$0 | 当前记录(存储着整个行的内容) |
$1~$n | 当前记录的第n个字段,字段之间由FS分隔 |
FS | 输入字段分隔符,默认是空格或Tab |
NF | 当前记录中的字段个数,即列数 |
NR | 已读出的记录数,即行号,从1开始。如果处理多个文件,这个值会不断累加 |
FNR | 当前记录数,和NR不同的是,这个值会针对每个文件单独计算行号 |
RS | 输入记录分隔符,默认为换行符 |
OFS | 输出字段分隔符,默认为空格 |
ORS | 输出记录分隔符,默认为换行符 |
FILENAME | 当前输入文件的名字 |
如何使用这些内建变量呢?例如,如果我们需要输出行号:
$ awk '$3==0 && $6=="ESTABLISHED" || NR==1 {printf "%02s %s %-20s %-20s %s\n",NR, FNR, $4,$5,$6}' netstat.txt 01 1 Local-Address Foreign-Address State 07 7 xinxiyishu.com:80 66.194.134.189:1032 ESTABLISHED 08 8 xinxiyishu.com:80 219.169.233.111:56231 ESTABLISHED 10 10 xinxiyishu.com:80 219.169.233.111:32167 ESTABLISHED 14 14 xinxiyishu.com:80 66.194.134.189:4796 ESTABLISHED 17 17 xinxiyishu.com:80 219.169.233.111:78352 ESTABLISHED
指定分隔符
$ awk 'BEGIN{FS=":"} {print $1,$3,$6}' /etc/passwd root 0 /root bin 1 /bin daemon 2 /sbin adm 3 /var/adm lp 4 /var/spool/lpd sync 5 /sbin shutdown 6 /sbin halt 7 /sbin
上述命令也可以写成下面这种形式:
(-F参数用于指定分隔符)
$ awk -F: '{print $1,$3,$6}' /etc/passwd
注意:如果需要指定多个分隔符,可以使用以下方式:
awk -F '[;:]'
接下来,我们来看一个使用\t作为分隔符的输出示例(以下示例使用了/etc/passwd文件,文件中的字段由:分隔):
$ awk -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd root 0 /root bin 1 /bin daemon 2 /sbin adm 3 /var/adm lp 4 /var/spool/lpd sync 5 /sbin
进两步
字符串匹配
接下来,我们来看几个字符串匹配的例子:
$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 1 Local-Address Foreign-Address State 6 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 9 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 13 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 18 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2 $ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 1 Local-Address Foreign-Address State 5 xinxiyishu.com:80 233.210.5.146:10099 TIME_WAIT 6 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 9 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 11 xinxiyishu.com:80 76.60.215.36:36970 TIME_WAIT 13 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 15 xinxiyishu.com:80 76.60.212.163:51082 TIME_WAIT 18 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2
第一个示例匹配了FIN状态,第二个示例匹配了WAIT状态。这里的 ~ 符号表示模式的开始,/ /中的内容则是正则表达式的匹配模式。
实际上,awk可以像grep一样用于匹配第一行,示例如下:
$ awk '/LISTEN/' netstat.txt tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN
我们可以通过使用“/FIN|TIME/”来匹配包含FIN或TIME的行:
$ awk '$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 1 Local-Address Foreign-Address State 5 xinxiyishu.com:80 233.210.5.146:10099 TIME_WAIT 6 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 9 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 11 xinxiyishu.com:80 76.60.215.36:36970 TIME_WAIT 13 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 15 xinxiyishu.com:80 76.60.212.163:51082 TIME_WAIT 18 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2
接下来,我们来看一个模式取反的例子:
$ awk '$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt 1 Local-Address Foreign-Address State 2 0.0.0.0:3306 0.0.0.0:* LISTEN 3 0.0.0.0:80 0.0.0.0:* LISTEN 4 127.0.0.1:9000 0.0.0.0:* LISTEN 7 xinxiyishu.com:80 66.194.134.189:1032 ESTABLISHED 8 xinxiyishu.com:80 219.169.233.111:56231 ESTABLISHED 10 xinxiyishu.com:80 219.169.233.111:32167 ESTABLISHED 12 xinxiyishu.com:80 155.148.242.38:30901 ESTABLISHED 14 xinxiyishu.com:80 66.194.134.189:4796 ESTABLISHED 16 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK 17 xinxiyishu.com:80 219.169.233.111:78352 ESTABLISHED 19 :::22 :::* LISTEN
你也可以使用简化的形式:
awk '!/WAIT/' netstat.txt
折分文件
使用awk拆分文件非常简单,只需要使用重定向操作即可。下面的例子展示了如何按第6列来拆分文件,这个过程非常直接(其中,NR!=1表示不会处理表头)。
$ awk 'NR!=1{print > $6}' netstat.txt $ ls ESTABLISHED FIN_WAIT1 FIN_WAIT2 LAST_ACK LISTEN netstat.txt TIME_WAIT $ cat ESTABLISHED tcp 0 0 xinxiyishu.com:80 66.194.134.189:1032 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:56231 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:32167 ESTABLISHED tcp 0 4166 xinxiyishu.com:80 155.148.242.38:30901 ESTABLISHED tcp 0 0 xinxiyishu.com:80 66.194.134.189:4796 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:78352 ESTABLISHED $ cat FIN_WAIT1 tcp 0 1 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 $ cat FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2 $ cat LAST_ACK tcp 0 1 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK $ cat LISTEN tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN $ cat TIME_WAIT tcp 0 0 xinxiyishu.com:80 233.210.5.146:10099 TIME_WAIT tcp 0 0 xinxiyishu.com:80 76.60.215.36:36970 TIME_WAIT tcp 0 0 xinxiyishu.com:80 76.60.212.163:51082 TIME_WAIT
你也可以将指定的列输出到文件中:
awk 'NR!=1{print $4,$5 > $6}' netstat.txt
如果需求稍微复杂一些,可以使用以下方式:(注意其中的if-else-if语句,显示了awk实际上是一个脚本解释器)
$ awk 'NR!=1{if($6 ~ /TIME|ESTABLISHED/) print > "1.txt"; else if($6 ~ /LISTEN/) print > "2.txt"; else print > "3.txt" }' netstat.txt $ ls ?.txt 1.txt 2.txt 3.txt $ cat 1.txt tcp 0 0 xinxiyishu.com:80 233.210.5.146:10099 TIME_WAIT tcp 0 0 xinxiyishu.com:80 66.194.134.189:1032 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:56231 ESTABLISHED tcp 0 0 xinxiyishu.com:80 219.169.233.111:32167 ESTABLISHED tcp 0 0 xinxiyishu.com:80 76.60.215.36:36970 TIME_WAIT tcp 0 4166 xinxiyishu.com:80 155.148.242.38:30901 ESTABLISHED tcp 0 0 xinxiyishu.com:80 66.194.134.189:4796 ESTABLISHED tcp 0 0 xinxiyishu.com:80 76.60.212.163:51082 TIME_WAIT tcp 0 0 xinxiyishu.com:80 219.169.233.111:78352 ESTABLISHED $ cat 2.txt tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN $ cat 3.txt tcp 0 0 xinxiyishu.com:80 155.140.99.185:21345 FIN_WAIT2 tcp 0 0 xinxiyishu.com:80 221.234.127.77:16802 FIN_WAIT2 tcp 0 1 xinxiyishu.com:80 233.152.181.213:23123 FIN_WAIT1 tcp 0 1 xinxiyishu.com:80 192.168.113.92:10099 LAST_ACK tcp 0 0 xinxiyishu.com:80 213.136.20.85:43215 FIN_WAIT2
统计
下面的命令用于计算所有C文件、CPP文件和H文件的文件大小总和。
$ ls -l *.cpp *.c *.h | awk '{ sum+=$5} END {print sum}' 2511401
接下来,演示一个统计各个连接状态的用法:(这里我们可以看到一些编程的影子,大家都作为程序员了,应该能理解其中的原理,不做过多解释。注意其中数组的使用)
$ awk 'NR!=1{a[$6]++;} END {for (i in a) print i ", " a[i];}' netstat.txt TIME_WAIT, 3 FIN_WAIT1, 1 ESTABLISHED, 6 FIN_WAIT2, 3 LAST_ACK, 1 LISTEN, 4
再看一个统计每个用户进程占用内存的例子(注:RSS列表示进程占用的内存)
$ ps aux | awk 'NR!=1{a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}' dbus, 540KB mysql, 99928KB www, 3264924KB root, 63644KB Tom, 6020KB
进阶步骤
awk脚本
在前面的例子中,我们使用了END关键字。END的作用是“处理完所有行之后执行”。既然提到了END,就需要介绍一下BEGIN,这两个关键字分别表示执行前和执行后的意思,语法如下:
- BEGIN{这里放的是执行前的语句}
- END{这里放的是处理完所有行后的语句}
- {这里放的是处理每一行时要执行的语句}
为了更好地理解,我们来看一下下面的示例:
假设我们有一个学生成绩表:
$ cat score.txt Marry 2143 78 84 77 Jack 2321 66 78 45 Tom 2122 48 77 71 Mike 2537 87 97 95 Bob 2415 40 57 62
我们的awk脚本如下(我没有直接在命令行写脚本,是因为在命令行中较难阅读,另外也想介绍另一种写法):
$ cat cal.awk #!/bin/awk -f # 执行前 BEGIN { math = 0 english = 0 computer = 0 printf "NAME NO. MATH ENGLISH COMPUTER TOTAL\n" printf "---------------------------------------------\n" } # 执行中 { math+=$3 english+=$4 computer+=$5 printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5 } # 执行后 END { printf "---------------------------------------------\n" printf " TOTAL:%10d %8d %8d \n", math, english, computer printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR }
接下来查看执行结果:(也可以通过命令 ./cal.awk score.txt 来执行)
$ awk -f cal.awk score.txt NAME NO. MATH ENGLISH COMPUTER TOTAL --------------------------------------------- Marry 2143 78 84 77 239 Jack 2321 66 78 45 189 Tom 2122 48 77 71 196 Mike 2537 87 97 95 279 Bob 2415 40 57 62 159 --------------------------------------------- TOTAL: 319 393 350 AVERAGE: 63.80 78.60 70.00
环境变量
既然提到了脚本,接下来我们来看看如何与环境变量进行交互。我们可以通过使用 `-v` 参数和 `ENVIRON` 来实现(需要使用 `export` 导出环境变量)。
$ x=5 $ y=10 $ export y $ echo $x $y 5 10 $ awk -v val=$x '{print $1, $2, $3, $4+val, $5+ENVIRON["y"]}' OFS="\t" score.txt Marry 2143 78 89 87 Jack 2321 66 83 55 Tom 2122 48 82 81 Mike 2537 87 102 105 Bob 2415 40 62 72
几个技巧
最后,我们来看一些小技巧:
# 从file文件中找出长度大于80的行 awk 'length>80' file # 按连接数查看客户端IP netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr # 打印99乘法表 seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'
好的学习资源
如果你想深入了解上述知识点,以下是一些有用的学习资源:
- 内建变量,请参阅:gawk的内建变量
- 流控语句,请参阅:gawk的流控语句
- 内建函数,请参阅:gawk的内建函数
- 正则表达式,请参阅:gawk的正则表达式
AWK 的内容学到这里已经满足常规使用了,希望对你在此基础上更进一步。
阅读完毕,很棒哦!
上一篇:架构师的核心能力:抽象能力