Linux 运维实战系列——文本处理三剑客之一 awk
命令小结
awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有很多其他的功能。
作为一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。数据可以来自标准输入、外部文件,或者其他命令的输出。awk 同时支持正则表达式和用户自定义函数。
awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息(ctrl+c/v的,未考证😳)。
实战经验
入门
指定输出列
awk 处理文件是以记录为单位的,它会一条记录一条记录地遍历文件并进行处理。$0 代表的是一条记录,也就是student.txt 文件中的一整行内容。$1, 和 $3 代表的是第一、三列。
1 | [baiyongan@bya awk_test]$ cat student.txt |
修饰表头和表尾
两个关键字:
BEGIN:在awk对文件处理之前首先被执行
END:表示在awk对所有文件处理完后才被执行
1 | [baiyongan@bya awk_test]$ awk 'BEGIN {print "Name province\n----------------------------"} {print $1, $3} END {print "----------------------------"}' student.txt |
统计功能
如下:可以用 $4 + $5 + $6 来实现总分的计算
1 | [baiyongan@bya awk_test]$ cat student.txt |
筛选功能
可以使用类似 $3 ~ /beijing/ 的语法筛选出来来自beijing的学生的信息。
1 | [baiyongan@bya awk_test]$ awk 'BEGIN {print "Name province\n----------------------------"} $3 ~ /beijing/ {print $1, $3, $4+$5+$6} END {print "---------------------------------"}' student.txt |
语法格式
简版:awk ‘Pattern {Action}’ filename
其中:
Pattern 用来指定判断条件,比如上面例子中的 $3 ~ /beijing/
{} 中包含的是 awk 的动作,也就是 awk 对记录的操作,比如 $4+$5+$6 或者 print
工作原理
- 第一步,awk 读取一条记录(文件中的一行内容)作为输入,并将这条记录赋值给内部变量 $0
- 第二步,记录被分隔符分割成多个字段,每一个字段都存储到指定编号的变量中,从 $1 开始。(awk 的内部变量 FS 用来指定字段的分隔符。默认情况下,为空格,包含制表符和空格符,也可以用 - F 来人为指定分隔符 )
- 第三步,对每一条记录,按 Pattern 进行匹配,匹配成功,则执行对应 Action,匹配失败,则不执行Action。
- Pattern 和 Action都可选,但是必须提供其中一个。没有指定 Pattern,则对所有的输入行都执行 Action 操作。如果没有指定 Action,则会输出匹配行的内容。
- 如果Action 被指定为 {}, 则表示不做任何动作,也不会输出匹配行的内容。
- 第四步,重复进行上面的 1-3 步直到文件结束。
更完整的格式与工作流程
详版:awk ‘BEGIN{ commands } Pattern { commands } END{ commands }’
- 通过关键字 BEGIN 执行 BEGIN 块的内容,即 BEGIN 后花括号 {} 的内容。
- 完成 BEGIN 块的执行,开始执行body块。
- 读入有 换行符分割的记录。
- 将记录按指定的域分隔符划分域,填充域,$0 则表示所有域(即一行内容),$1 表示第一个域,$n 表示第 n 个域。
- 依次执行各 BODY 块,pattern 部分匹配该行内容成功后,才会执行 awk-commands 的内容。
- 循环读取并执行各行直到文件结束,完成body块执行。
- 开始 END 块执行,END 块可以输出最终结果。
awk 脚本编写
如果需要在Action的部分里写打断的逻辑,建议将器写道一个独立的脚本文件中。
1 | [baiyongan@bya awk_test]$ cat student.awk |
如果要让awk脚本执行得“更像样子”:可以在awk脚本开头加上如下一句话。#! /bin/awk -f
1 | [baiyongan@bya awk_test]$ sed -i '1i\#! /bin/awk -f' student.awk |
多行操作
显示奇数行
NR 表示行数,如下案例,当行数为偶数时,不输出,当行数为奇数时,输出。
NR The total number of input records seen so far.
next Stop processing the current input record. The next input record is read
and processing starts over with the first pattern in the AWK program.
If the end of the input data is reached, the END block(s), if any, are
executed.
1 | [baiyongan@bya awk_test]$ cat -n input.txt |
固定行数合并
如果将文件中的每三行数据合并为一行,如何操作呢?
1 | [baiyongan@bya awk_test]$ cat -n input.txt |
解释:
第一部分:NR%3!=0 {T=(T” “$0); next}
第二部分:{print T, $0; T=””}
T=(T” “$0) 表示将字符型的变量T,空格和变量 $0,三者的值进行连接,然后再赋值给变量T。也就是说,awk 语句每读取一行都会把这行的内容追加到变量T中。
next 的作用很关键,表示不再执行第二部分的语句,而是直接进行下一行的处理,这样就保证awk把每三行一组的内容都存储到了变量T中。
{print T, $0; T=””} 表示输出变量T和$0的内容,并把变量T的内容清空。
注意:最后一行的数据ten没有输出出来,其实时最后一行没有达到 “3个一组”的条件。如果需要,可以再最后增加END操作,进行收尾
1 | [baiyongan@bya awk_test]$ awk 'NR%3!=0 {T=(T" "$0); next} {print T, $0; T=""} END {print T}' input.txt |
不定行数合并
有时候的分组合并场景,并不都是固定行数的合并,不定行数应该怎么办呢?如下,对于不同时间,可以用worktime来分组:
1 | [baiyongan@bya awk_test]$ awk 'BEGIN {T=""} /worktime/ {print T; T=$0; next} {T=T""$0} END {print T}' input_time.txt |
合并所有的行
合并一个文件的所有行,经常碰到,linux中实现这类操作的方法很多,可以用sed 直接删除文件中的换行符,当然,也可以用awk来实现。
1 | [baiyongan@bya awk_test]$ cat input.txt |
多文件操作
多文件输出
Q:如何将两个文件一起输出呢?
A:直接再命令尾部加上文件名即可
1 | [baiyongan@bya awk_test]$ awk '{print $0}' input.txt input_time.txt |
两手抓两个文件
Q:如果要输出 input.txt 文件中的第一行数据,input_time.txt 的第八行数据以及它们的文件名,应该怎么实现呢?
A:如下所示
NR 表示已读记录数,无论有几个文件,每读一条记录,值会增加 1;
FNR 表示当前文件的已读记录数,操作当前文件时,每读一条记录,值会增加 1。但当更换文件后,该变量会重新从零开始。
当 NR==FNR时,表示awk正在处理第一个输入文件。当 NR>FNR时,表示正在处理第二个输入文件。
FILENAME表示当前处理文件的文件名。
NR The total number of input records seen so far.
FNR The input record number in the current input file.
FILENAME The name of the current input file. If no files are specified on the command
line, the value of FILENAME is “-”. However, FILENAME is undefined inside the
BEGIN block (unless set by getline).
1 | [baiyongan@bya awk_test]$ awk '{print $0}' input.txt input_time.txt |
多只手抓多个文件
Q:如何区分多个文件呢?
A:针对多个文件,awk里有个专门的环境变量 ARGIND ,用来指定当前正在处理的文件的编号
ARGIND The index in ARGV of the current file being processed.
1 | [baiyongan@bya awk_test]$ cat input.txt input_time.txt input_3.txt |
除了上述的方法,还可以在文件名上下手,例如:
- FILENAME
- ARGV:命令行参数的数组,同C语言的main(int argc, char **argv)
ARGV Array of command line arguments. The array is indexed from 0 to ARGC - 1. Dynam‐
ically changing the contents of ARGV can control the files used for data.
1 | [baiyongan@bya awk_test]$ |
合并两个文件
Q: /etc/passwd 中的第二个域是用 x 来代替的,表示账户对应的密码,/etc/shadow 文件中第二个域,则存储着具体的密码信息,如何用密码来替换掉 x 呢?
A:
第一部分:BEGIN {OFS=FS=”:”}
第二部分:NR==FNR {a[$1]=$2}
第三部分:NR>FNR {$2=a[$1]; print}
- 第一部分,设定OFS(输出字段分隔符) 和 FS(字段分隔符) 都为 “:”。
- 第二部分,NR==FNR {a[$1]=$2}。其中NR==FNR 表示awk输入的第一个文件。{a[$1]=$2}执行,则awk会把文件的第二个字段,存放到下标为$1(文件的第一个字段的值)的数组a中。
- 第三部分,NR>FNR {$2=a[$1]; print}。其中,NR>FNR表示awk输入的第二个文件。桑awk在处理第二个文件时, {$2=a[$1]; print},awk会把下标为$1(文件的第一个字段的值)的数组a 中取出内容赋值给 $2 (文件的第一个字段),最后输出替换后的文件内容。
1 | [root@bya awk_test]# cat /etc/shadow | head -n 5 |
合并两文件并求和
1 | [root@bya awk_test]# cat 1.txt |
外部调用
实现awk 外部调用的两种方式:
- 通过getline 引用外部数据;
- 通过 system 调用 shell的命令。
getline操作文件的困惑
getline Set $0 from next input record; set NF, NR, FNR.
getline <file Set $0 from next record of file; set NF.
getline var Set var from next input record; set NR, FNR.
getline var <file Set var from next record of file.
command | getline [var]
Run command piping the output either into $0 or var, as above.command |& getline [var]
Run command as a co-process piping the output either into $0 or var, as above. Co-processes are a gawk extension. (command can also be a socket. See the subsection Special
File Names, below.)
当只有 getline时,getline是针对当前打开的文件操作,表示读取当前行的下一行数据,并把读取到的数据赋值给$0,同时更新 NF,NR,FNR。
因此,
‘{print; getline}’ 输出奇数行
‘{getline; print}’ 输出偶数行
‘{print; getline; print}’ 输出全部行
1 | [root@bya awk_test]# cat -n test | awk '{print}' |
在getline之后,添加变量 b,则有:
1 | [root@bya awk_test]# cat -n test |
- getline 后面跟着变量b,表示获取到的数据不会赋值给$0,而会给变量b;
- 两个print 用来输出 $0,$0的数据没有改变,则输出了重复的字母。
如下,已经不需要解释,就可以正确解读了
1 | [root@bya awk_test]# cat -n test | awk '{print; getline b; print; print b}' |
getline操作文件的返回值
先看如下案例,while语句表示,当文件test输出一行,文件 1.txt 的内容就要重新输出一遍,但为什么只有第一行是这样的,而其他行,1.txt的内容完全没有输出呢?
1 | [root@bya awk_test]# cat 1.txt |
其实,getline的返回值:
- 1:表示正常读取一行数据。
- 0:表示到了文件末尾。
- -1:表示读取遇到错误。
p.s. getline重定向文件时,后面必须跟字符串类型。因此,文件1.txt一定要加上双引号!
用system 调用 shell
awk可以直接通过system()调用shell,但是有两点要注意:
- awk 无法直接将脚本中的数据直接输出给shell命令;
- shell 命令的执行结果也无法直接输入到awk脚本中。
1 | [root@bya awk_test]# awk 'BEGIN { system("ls -l")}' |
实战
其实,将awk 与其他命令结合起来,才能体现出awk的强大之处。例如结合下面的网络相关的命令
- ifconfig
- netstat
- tcpdump
- /proc/net/dev
显示本机的IP
统计网卡的流量
查看TCP连接状态
查找请求数排名前五的IP地址
1 | [root@bya awk_test]# netstat -anlp | grep 80 | grep tcp | awk '{print $5}' | awk -F : '{print $1}' | sort | uniq -c |sort -nr | head -n 5 |
用tcpdump嗅探80端口的访问
tcpdump 是 Linux工程师非常常用的流量分析工具。
锁定 time_wait 连接较多的源IP
根据端口列进程
补充
黑体的部分掌握即可应付80%的场景
awk的功能
命令格式 + 工作原理 + 执行过程
常用选项
内置变量
运算
高级输入输出功能
循环结构
数组
内置函数
字符串函数
时间函数
其他一般函数