全面详解shell编程三剑客之一的awk命令
shell编程三剑客里,awk比另外两个命令grep、sed更加复杂,更加难以掌握,因为awk是可以作为一个编程语言的!难归难,但该命令是必须要掌握的命令,因为它的功能实在太强大了!
基本结构
awk基本结构如下:
awk [选项] 'pattern1 {action1} patten2 {action2} ……' filename
- 单引号是为了和shell命令区分开来。
- 大括号表示一个命令分组。
- pattern是模式,表示匹配到的行才进行action
- pattern和action可以只有其一,但不能两者都没
awk常用的选项如下:
- -F:指定分割符,分割符可以是字符也可以是一个正则表达式
- -v val=value,定义一个变量并赋值
模式
模式即基本结构中的pattern部分。它可以是下面的几种之一:
- 正则表达式
- 关系表达式,比如>、=等
- 模式匹配表达式:用运算符
~
(匹配)和~!
(不匹配)。 - BEGIN语句块、pattern语句块、END语句块。参考下面的awk工作原理
动作
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:
- 变量或数组赋值
- 输出命令
- 内置函数
- 控制流语句
工作原理
awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
- 第一步:执行BEGIN{ commands }语句块中的语句;
- 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,
并将每行数据读入$0(表示一整行数据)、$1、$2等变量当中
从第一行到最后一行重复这个过程,直到文件全部被读取完毕。 - 第三步:当读至输入流末尾时,执行END{ commands }语句块。
BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。
END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。
pattern语句块中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。
范例一:
# echo -e "hello\nworld" | awk 'BEGIN{print "STRAT"} {print} END{print "END"}'
STRAT
hello
world
END
范例二、print中双引号可作为拼接符使用,next可以忽略当前行继续下面的行
# df -h | awk 'NR==1 {next} {print $1"\t"$2}'
/dev/vda1 40G
devtmpfs 487M
tmpfs 497M
tmpfs 497M
tmpfs 497M
tmpfs 100M
内置变量
- $n 当前记录的第n个字段,比如n为1表示第一个字段,n为2表示第二个字段。
- $0 这个变量包含执行过程中当前行的文本内容。
- FS 字段分隔符(默认是任何空格)。
- NF 表示字段数,在执行过程中对应于当前的字段数。
- NR 表示记录数,在执行过程中对应于当前的行号。
使用print $NF可以打印出一行中的最后一个字段,使用$(NF-1)则是打印倒数第二个字段,其他以此类推:
# echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | awk '{print "Line No:"NR", No of fields:"NF, "$0="$0, "$1="$1, "$2="$2, "$3="$3}'
Line No:1, No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
Line No:2, No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
Line No:3, No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7
# echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $NF}'
f3
f5
# echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $(NF-1)}'
f2
f4
逻辑运算
运算单元 | 代表意义 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
== | 等于 |
!= | 不等于 |
在/etc/passwd 当中是以冒号":" 来作为栏位的分隔, 该档案中第一栏位为帐号,第三栏位则是UID。那假设我要查阅,第三栏小于10 以下的数据,并且仅列出帐号与第三栏, 那么可以这样做:
# cat /etc/passwd | awk -F: '$3<=10 {print $1":"$3}'
root:0
bin:1
daemon:2
adm:3
lp:4
sync:5
shutdown:6
halt:7
mail:8
计算薪资总和
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
# cat pay.txt
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
# cat pay.txt | awk 'NR==1{printf "%-10s\t%-10s\t%-10s\t%-10s\tTOTAL\n", $1, $2,$3,$4} \
> NR>=2{total=$2+$3+$4;printf "%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", $1, $2,$3,$4,total }'
Name 1st 2nd 3th TOTAL
VBird 23000 24000 25000 72000
DMTsai 21000 20000 23000 64000
Bird2 43000 42000 41000 126000
awk 的指令间隔:所有awk 的动作,亦即在{} 内的动作,如果有需要多个指令辅助时,可利用分号『;』间隔,或者直接以[Enter] 按键来隔开每个指令,例如上面的范例中,我使用了;
逻辑运算当中,如果是『等于』的情况,则务必使用两个等号『==』!
格式化输出时,在printf 的格式设定当中,务必加上\n ,才能进行分行!如果是print则不用,默认会有换行。
与bash shell 的变量不同,在awk 当中,变数可以直接使用,不需加上$ 符号。所以,那个total前没有用$。
一个应用
一个文本部分内容如下:
# head city.txt
北京 BEIJING BJ
上海 SHANGHAI SH
天津 TIANJIN TJ
重庆 CHONGQING ZQ
阿克苏 AKESU AKS
……
我们来过滤空白行。
awk 'NR%2==0{next}{print}' city.txt