awk是一门编程语言,它支持条件判断、数组、循环等功能。所以,我们也可以把awk理解成一个脚本语言解释器。

grep 、sed、awk被称为linux中的"三剑客"。

这三个"剑客"各有特长。

grep 适合单纯的查找或匹配文本

sed  适合编辑匹配到的文本

awk  适合格式化文本,对文本进行较复杂格式处理。报告生成器,格式化文本输出。

AWK处理流程

image

我们现在在linux的所使用的awk其实是gawk,也就是GNU awk,简称为gawk。在系统内使用awk和gawk其实是一样的

#ll /bin/awk
lrwxrwxrwx. 1 root root 4 Dec 13 20:15 /bin/awk -> gawk
基本用法:
awk [options] 'program' var=value file…
    其中 program 通常是被单引号引中。
awk [options] -f programfile var=value file…
awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ...

awk 程序通常由:BEGIN语句块program、 END语句块,共3部分组成

program又可以细分成pattern和action

• pattern   决定动作语句何时触发及触发事件
    比如说可以使用正则表达式,总之就是读取进来数据会满足指定条件
• action    对满足指定条件数据进行处理,放在{}内指明
    常用的2个动作就是打印到屏幕咯 print, printf。如果没有指定action动作,默认的动作就是 print $0

记录、域和分割符

记录

文件的每一行称为记录,一般情况下,换行就为一条记录,

#cat file 
line1-1:2:3@a:b:c:d@E:F:G

#awk '{print $0}' file 
line1-1:2:3@a:b:c:d@E:F:G
这里就是1行记录

如果以@作为记录的分隔符,那么这一行数据里其实就是3行记录
#awk 'BEGIN{RS="@"}{print $0}' awk 
line1-1:2:3
a:b:c:d
E:F:G

由分隔符分隔的字段,对于每个域标记为记$1,$2..$n称为域标识。$0为所有域,也就是整条记录。不要跟shell中的$0混淆。
也可以把域理解为列,字段

域分隔符变量:FS(field sign)

$0 表示显示整行 ,$NF表示当前记录分割后的最后一个域($0和$NF均为内置变量)
$NF表示最后一个字段,NF表示当前记录被分隔符切开以后,一共有几个字段。
假如一条记录被分成了4列,那么NF的值就是4,$NF的值就是$4,  而$4表示当前记录的第4个字段,也就是最后一列。那么每条记录的倒数第二个字段可以写为$(NF-1)。注意括号。

分隔符

输入记录分隔符 RS
默认:换行符

#awk -v RS="@" '{print $0}' file
a b  c     d
a b c d

---------------------------------------------

输出记录分隔符 ORS
默认:换行(\n)

#awk -v ORS="行分隔" '{print $0}' awk
line1-1:2:3@a:b:c:d@E:F:G行分隔line2-1:2:3@a:b:c:d@E:F:G行分隔

---------------------------------------------

字段分隔符 FS
默认:单个空格

使用使用2种格式:
#awk -F "@" '{print $2,$3}' file
a:b:c:d E:F:G

#awk -v FS="@" '{print $2,$3}' file
a:b:c:d E:F:G

---------------------------------------------

输出字段分隔符 OFS
默认:单个空格

#awk -F ":" -v OFS="===" '{print $1$2}' awk
line1-12
line2-12
注意那个,逗号
#awk -F ":" -v OFS="===" '{print $1,$2}' awk
line1-1===2
line2-1===2

awk工作原理

以文件passwd.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 
#awk -F: 'BEGIN {print "user","passwd","UID","GID"}/^root/{print $1,$2,$3,$4}END{print "file output complete" }' passwd.txt

现在通过分步演示说明上一条awk语句的执行过程:

第一步:执行BEGIN{action;… }语句块中的语句

#awk -F: 'BEGIN {print "user","passwd","UID","GID"}' passwd.txt
user passwd UID GID

并没有处理passwd.txt内容,仅仅是打印了字符串,通常是用来当表头

第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{action;… }语句块,它逐行扫描文件,
从第一行到最后一行重复这个过程,直到文件全部被读取完毕。

指定以:分隔符

#awk -F: 'BEGIN {print "user","passwd","UID","GID"}/^root/{print $1,$2,$3,$4}' passwd.txt 
user passwd UID GID
root x 0 0

将每行记录分割之后,
其中pattern指定条件为以root为行首,所以,只有一条记录是符合条件的
action 的执行操作就是print 第1,2,3,4字段内容

----------------------------------------------------------------------------------
#awk -F: 'BEGIN {print "user","passwd","UID","GID"}{print $1,$2,$3,$4}' passwd.txt 
user passwd UID GID
root x 0 0
bin x 1 1
daemon x 2 2

将每行记录分割之后,
其中pattern没有指定,所以,所有的记录都是符合条件的
action 的执行操作就是print 第1,2,3,4字段内容。注意每个字段是属于内置变量,不要用引号。"$1" 这样会被以字符串显示出来。

第三步:当读至输入流末尾时,执行END{action;…}

#awk -F: 'BEGIN {print "user","passwd","UID","GID"}/^root/{print $1,$2,$3,$4}END{print "file output complete" }' passwd.txt 
user passwd UID GID
root x 0 0
file output complete

最后只打印了一条信息在行末。

总结:

BEGIN语句块在awk开始从输入流中读取行之前被执行,
这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中

END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,
它也是一个可选语句块

pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块

print格式

print item1, item2, ...

 要点:

• (1) 逗号分隔符

• (2) 输出的各item可以字符串,也可以是数值;当前记录的字段

• (3) 字符串,和特殊的表示要用双引号。"srt" "\t" "\n"

• (4) 如省略item,相当于print $0 表示显示整行。

 示例:

#awk -F: '{print $0}' /etc/passwd 等价于 #awk -F: '{print}' /etc/passwd
#awk -F: '{print $1"\t"$3}' /etc/passwd
#tail -3 /etc/fstab |awk '{print $2,$4}'

awk变量

内置变量

-v 是声明变量

输入记录分隔符 RS
默认:换行符

#awk -v RS="@" '{print $0}' awk2
a b  c     d
a b c d

---------------------------------------------

输出记录分隔符 ORS
默认:换行(\n)

#awk -v ORS="行分隔" '{print $0}' awk
line1-1:2:3@a:b:c:d@E:F:G行分隔line2-1:2:3@a:b:c:d@E:F:G行分隔

---------------------------------------------

字段分隔符 FS
默认:单个空格

使用使用2种格式:
#awk -F "@" '{print $2,$3}' awk
a:b:c:d E:F:G
a:b:c:d E:F:G
#awk -v FS="@" '{print $2,$3}' awk
a:b:c:d E:F:G
a:b:c:d E:F:G

---------------------------------------------

输出字段分隔符 OFS
默认:单个空格

#awk -F ":" -v OFS="===" '{print $1$2}' awk
line1-12
line2-12
注意那个,逗号
#awk -F ":" -v OFS="===" '{print $1,$2}' awk
line1-1===2
line2-1===2

---------------------------------------------
NF:字段数量
#awk -F: '{print NF}' /etc/passwd
7

#awk -F: '{print $(NF-1)}' /etc/passwd
/root
---------------------------------------------
NR:记录号

#awk -F: '{print NR}' /etc/passwd   相当于打印行号
1
2
---------------------------------------------
FNR:分别统计各文件行数
#awk '{print FNR}' /etc/passwd /etc/fstab

---------------------------------------------
FILENAME:当前文件名
#awk '{print FILENAME}' /etc/fstab

---------------------------------------------
ARGC:命令行参数的个数,注意命令本身也算一个
#awk '{print ARGC}' /etc/fstab /etc/inittab
3

---------------------------------------------
ARGV:数组,保存的是命令行所给定的各参数

注意以下,ARGV[0],指的是命令本身
#awk 'BEGIN{print ARGV[0]}' /etc/passwd /etc/fstab 
awk
#awk 'BEGIN{print ARGV[1]}' /etc/passwd /etc/fstab 
/etc/passwd
#awk 'BEGIN{print ARGV[2]}' /etc/passwd /etc/fstab 
/etc/fstab

自定义变量

用户定义的变量,有两种方法可以自定义变量。

方法一:-v 变量名=变量值  变量名区分字符大小写。注意变量名的命名规则可以参考之前的bash编程
#awk  -v 1abc=hunk 'BEGIN{print 1abc}'  错误的变量命名
awk: fatal: `1abc' is not a legal variable name

#awk  -v _1abc=hunk 'BEGIN{print _1abc}'
hunk

方法二:在program中直接定义。
变量定义与动作(action)之间需要用分号";"隔开
#awk  'BEGIN{name="hunk";blog="http://blog.51cto.com/191226139";print name,blog}'
hunk http://blog.51cto.com/191226139

引用外部的bash变量
#var="bash-variable"

#awk 'BEGIN{print var}'
输出为空
看到了吧,直接引用是不可以的

下面使用-v 定义就能正常引用。所以-v 也是有特定用途的。
#awk -v var2=$var 'BEGIN{print var2}'
bash-variable

另外,字段分割符也可以引用bash变量
#awk -F "$var"  使用-F时,外部变量只能引有一次,后续就无法再次引用了
#awk -v FS="$var" 'BEGIN{print FS}'

总结


1.在awk中,只有在引用$0、$1等内置域(列)变量的值的时候才会用到"$",引用其他变量时,不管是内置变量,还是自定义变量,都不使用"$",而是直接使用变量名。

2.先定义变量,再使用。

3.awk 的 FS字段分隔符是支持正则表达式的,记得用双引号。
line1-1:2:3@a:b:c:d@E:F:G

#awk -F "[-:@]+" '{print $1,$2,$3,$4,$5,$6}' awk.txt
line1 1 2 3 a b
line2 1 2 3 a b

这里表示的是[ ] 内的字符- 或 :或 @ 可以作为分隔符,外面的+号是正则表达式的1个以上

4.awk 支持从文件读取指令
#cat awk_file 
{user="username";uid="UID";print user":"$1,uid":"$3}
#awk -F ":" -f awk_file /etc/passwd 
username:root UID:0
username:bin UID:1

利用printf 进行格式化输出

可以参考另一篇博客
printf 命令详解

printf 动作的用法与 printf 命令的用法非常相似,只是有略微的不同而已。
2点不同:

第一点:

在awk中使用printf动作时,指定的"格式"与列($1)之间需要用"逗号"隔开,
而使用printf命令时,指定的格式与传入的文本不需要使用"逗号"隔开

awk printf 动作 printf "FORMAT" ,item1, item2, item3...
普通命令 printf "FORMAT" item1 item2 item3 ...

#printf "%s\n" A B C
A
B
C

#echo "A B C"|awk '{printf "%s\n" $1}'  $1前没有逗号
awk: (FILENAME=- FNR=1) fatal: not enough arguments to satisfy format string
    `%s
A'
     ^ ran out for this one

#echo "A B C"|awk '{printf "%s\n",$1}'  $1前有逗号
A

第二点:

在awk中使用printf动作时,指定的"格式"与列($N)需要一一对应,而使用printf命令时,则不需要

awk printf 动作 printf "FORMAT FORMAT FORMAT " ,item1, item2, item3...
普通命令 printf "FORMAT" item1 item2 item3 ...

#printf "%s\n" A B C
A
B
C

#echo "A B C"|awk '{printf "%s\n%s\n%s\n",$1,$2,$3}' 格式与列一一对应
A
B
C

以上2点需要特别注意。

操作符

算术运算

x+y, x-y, x*y, x/y, x^y, x%y
-x: 转换为负数
    #awk 'BEGIN {printf "%d\n", -(3-1)}'
    -2
+x: 转换为数值

#awk 'BEGIN {print 3+5}'
8
#awk 'BEGIN {printf "%f\n",1/3}' 比bash要强,支持浮点数
0.333333

字符串操作符

没有符号的操作符

#awk 'BEGIN{print "abc-edf"}'

赋值操作符

=, +=, -=, *=, /=, %=, ^= ++, --

#awk 'BEGIN{i=10;m+=i;print i}'
10
#awk 'BEGIN{i=10;++i;print i}'
11
#awk 'BEGIN{unset i;i=10;++i;print i}'
11

比较操作符

操作符 含义 示例
< 小于 a < b
<= 小于等于 a <= b
== 等于 a == b
!= 不等于 a != b
>= 大于等于 a >= b
> 大于 a > b
~ 与正则匹配(包含的关系) a ~ b
!~ 与正则不匹配 a !~ b
#awk -F: '$3 < 10 {printf "%3d %-8s %6d\n", NR,$1,$3}' /etc/passwd
  1 root          0
  2 bin           1
  3 daemon        2
  4 adm           3
  5 lp            4
  6 sync          5
  7 shutdown      6
  8 halt          7
  9 mail          8

#awk -F: '$1 == "hunk" {print NR,$1,$3}' /etc/passwd
32 hunk 500

使用正则表达式时的注意要点

在使用如~这种匹配关系时,必须加双引号或者/ / 。与搜索匹配正则位置的语法不一样。

匹配正则,注意是包含的关系。
#awk '$0 ~ "root" {print NR,$1,$3}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash 
11 operator:x:11:0:operator:/root:/sbin/nologin 

不匹配正则
#awk '$0 !~ "/bin/bash" {print NR,$1,$3}' /etc/passwd
2 bin:x:1:1:bin:/bin:/sbin/nologin 
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin 
4 adm:x:3:4:adm:/var/adm:/sbin/nologin 
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin 
6 sync:x:5:0:sync:/sbin:/bin/sync 
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

awk命令在使用扩展正则表达式,需要将正则表达式放入了"/ /"中.如果正则中包含"/",则需要进行转义.比如说:

#df | awk '/\/dev\/sd/{print $0}'
/dev/sda3        8652808 2437492   5769108  30% /
/dev/sda1         487652   35608    426444   8% /boot

当使用 {a,b},{a,} 这种次数匹配的正则表达式时,需要配合--posix选项或者--re-interval选项

#awk '/(abc){2,2}/{print $0}' demo.txt
输出为空

#awk --re-interval '/(abc){2,2}/{print $0}' demo.txt 
abcabckkal

#awk --posix '/(abc){2,2}/{print $0}' demo.txt 
abcabckkal

#awk --re-interval '/(abc){2,}/{print $0}' demo.txt 
abcabckkal

这种写法貌似不支持
#awk --re-interval '/(abc){,2}/{print $0}' demo.txt 
输出为空

awk 的行范围模式

语法:
awk / 正则表达式1 /,/ 正则表达式2 /{动作}

#awk '/^lp/,/^games/{print NR,$0}' /etc/passwd
匹配以lp为行首到以games行首之间的范围
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
11 operator:x:11:0:operator:/root:/sbin/nologin
12 games:x:12:100:games:/usr/games:/sbin/nologin

逻辑操作符

&& (与),|| (或),! (非)

#seq 1 10 | awk '$1 >= 3 && $1 <= 6 {print $0}'         && (与)
3
4
5
6

#seq 1 10 | awk '$1 == 3 || $1 == 6 {print $0}'         || (或)
3
6

#seq 1 10 | awk '!($1 < 5) {print $0}'                  ! (非)
5
6
7
8
9
10