一、简介

        awk是逐行处理的,当awk处理一个文本时,会一行一行进行处理,处理完当前行,再处理下一行,awk默认以”换行符”为标记,识别每一行,新的一行的开始,awk会按照用户指定的分割符去分割当前行,如果没有指定分割符,默认使用空格作为分隔符
        从字面上理解 ,action指的就是动作,awk擅长文本格式化,并且将格式化以后的文本输出,所以awk最常用的动作就是print和printf

二、基本语法格式

格式: awk [options] 'Pattern{Action}' file

举例: awk -F":" 'NR==5{ print $1,$2,$NF,"自定义添加字段" }' 文件

选项:

                -F                    指定分隔符,默认分隔符为空格
                NR                   指定第几行 df -Th | awk F" " 'NR==2{print $6}'  取出第二行第六列
                $0                    全部的列
                $NF                 最后一列,$(NF-1) 倒数第二列,NF 会显示有多少列
                $1,$2...$n         第一列 第二列 第n列,需要用逗号隔开,输出的列之间会有空格

三、Pattern模式

AWK 包含两种特殊的模式:BEGIN 和 END

BEGIN 模式指定了处理文本之前需要执行的操作
END 模式指定了处理完所有行之后所需要执行的操作

举例:
处理文本前先输出aaa bbb
# awk -F":" 'BEGIN{ print "aaa","bbb" }' passwd 
aaa bbb

输出后对文本进行处理
# awk -F":" 'BEGIN{ print "aaa","bbb" } NR==1{ print $1,$3}' passwd 
aaa bbb
root 0

文本处理结束后进行的动作
# awk -F":" 'BEGIN{ print "aaa","bbb" } NR==1{ print $1,$3} END{ print "ccc","ddd" }' passwd
aaa bbb
root 0
ccc ddd

由此看来,最终的返回结果很像一张表,有 "表头","表内容","表尾"
# awk -F":" 'BEGIN{ print "表的头部" } NR==1{ print $1,$3} END{ print "表的尾部" }' passwd
表的头部
root 0
表的尾部

添加了printf格式化处理

# awk -F":" 'BEGIN{ printf "%s\t\t %s\n" , " ","头部" }BEGIN{ printf "%-10s\t %-10s\t %-5s\n","name","UID","SHELL" }{ printf "%-10s\t %-10s\t %-5s\n", $1,$3,$NF } END{ printf "%s\t\t %s\n", " ","尾部" }' passwd 

四、分隔符

awk分为输入分隔符FS和输出分隔符OFS

输入分隔符

        awk逐行处理文本的时候,以输入分隔符为准,将文本切成多个片段,默认使用空格,也可以指定使用":"作为输入分隔符
# awk -F":" 'NR==1{ print $1,$3 }' passwd
root 0

        除了使用 -F 选项指定输入分隔符,还能够通过设置内部变量的方式,指定awk的输入分隔符
awk内置变量FS可以用于指定输入分隔符,但是在使用变量时,需要使用-v选项,用于指定对应的变量
# awk -v FS=":" 'NR==1{ print $1,$3 }' passwd
root 0

回顾一下我们awk的格式
# awk [options] 'Pattern{Action}' file

-F 就是options的一种,用于指定输入分隔符
-v 也是options的一种,用于设置变量的值

输出分隔符

        awk输出每一列的时候,会使用空格隔开每一列,其实,这个空格,就是awk的默认的输出分隔符
可以使用awk的内置变量OFS来设定awk的输出分隔符,当然,使用变量的时候要配合使用-v选项
# awk -v FS=":" -v OFS="---" 'NR==1{ print $1,$3 }' passwd
root---0

当打印多列的时候,需要用逗号隔开才能指定输出分隔符,如果没有逗号,两列将会合并在一起显示
# awk -v FS=":" -v OFS="---" 'NR==1{ print $1$3 }' passwd
root0
# awk -v FS=":" -v OFS="---" 'NR==1{ print $1 $3,$NF }' passwd 
root0---/bin/bash

五、awk变量

FS:输入字段分隔符, 默认为空白字符
OFS:输出字段分隔符, 默认为空白字符

NR:行号,当前处理的文本行的行号
NF:number of Field,当前行的字段的个数(即当前行被分割成了几列),字段数量
RS:输入记录分隔符(输入换行符), 指定输入时的换行符
ORS:输出记录分隔符(输出换行符),输出时用指定符号代替换行符
FNR:各文件分别计数的行号
FILENAME:当前文件名
ARGC:命令行参数的个数
ARGV:数组,保存的是命令行所给定的各参数

内置变量NR

显示文本的行数

# awk -F":" '{ print NR }' passwd
1
2
3
4
5
# awk -F":" 'NR==2{ print NR,$0 }' passwd 
2 bin:x:1:1:bin:/bin:/sbin/nologin

内置变量NF

文本一共有几行,每一行有几列

# awk -F":" '{ print NR,NF }' passwd   
1  7
2  7
3  7
4  7
5  7

内置变量FNR

当awk处理两个文本时
# awk -F":" '{ print NR,$1 }' passwd /etc/passwd
1 root
2 bin
3 daemon
4 adm
5 lp
6 root
7 bin
8 daemon
9 adm
10 lp
# awk -F":" '{ print FNR,$1 }' passwd /etc/passwd
1 root
2 bin
3 daemon
4 adm
5 lp
1 root
2 bin
3 daemon
4 adm
5 lp

内置变量FILENAME

# awk -F":" '{ print FILENAME,FNR,$1 }' passwd /etc/passwd
passwd 1 root
passwd 2 bin
passwd 3 daemon
passwd 4 adm
passwd 5 lp
/etc/passwd 1 root
/etc/passwd 2 bin
/etc/passwd 3 daemon
/etc/passwd 4 adm
/etc/passwd 5 lp

内置变量RS

RS是输入行分隔符,如果不指定,默认的”行分隔符”就是我们所理解的"回车换行"

# vim test.txt
aaa bbb ccc ddd
tom jerry zhangsan lisi

每次遇到一个空格就是新的一行,对于tom为回车换行,但是awk并不认为是换行符,只有空格才是换行符,在awk眼中,4 ddd\ntom为同一行
# awk -v RS=" " '{ print NR,$0 }' test.txt
1 aaa
2 bbb
3 ccc
4 ddd
tom
5 jerry
6 zhangsan
7 lisi

内置变量ORS

awk默认把"回车换行 "当做" 输出行分隔符,但是我们可以改变默认的输出行分隔符为+++,在awk眼中,+++就是输出换行符
# awk -v RS=" " -v ORS="+++" '{ print NR,$0 }' test.txt
1 aaa+++2 bbb+++3 ccc+++4 ddd
tom+++5 jerry+++6 zhangsan+++7 lisi

内置变量ARGV和ARGC

ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数
# awk -F":" 'BEGIN{ print ARGV[0],ARGV[1],ARGV[2] }' passwd /etc/passwd
awk passwd /etc/passwd

ARGV[0]   表示的是awk本身
ARGV[1]   第一个文件名
ARGV[2]   第二个文件名

上述例子中三个参数为awk、passwd、/etc/passwd,这三个参数作为数组的元素存放于ARGV中,而ARGC则表示参数的数量,也可以理解为ARGV数组的长度
# awk -F":" 'BEGIN{ print ARGV[0],ARGV[1],ARGV[2],ARGC }' passwd /etc/passwd
awk passwd /etc/passwd 3

自定义变量

方法一:-v varname=value  变量名区分字符大小写
例:
# awk -v i=1 'BEGIN{ print i }'

方法二:在program中直接定义,变量定义与动作之间需要用分号";"隔开
例:
# awk 'BEGIN{ i=1 ; j=2 ; print i"\n"j }'
1
2

六、正则模式

格式:
# awk '/正则表达式/{ print $0 }' passwd

匹配以root开头,打印所有的列
# awk '/^root/{ print $0}' passwd 
root:x:0:0:root:/root:/bin/bash

匹配以/bin/bash结尾,打印所有的列,需要对/进行转义\
# awk '/\/bin\/bash$/{ print $0}' passwd
root:x:0:0:root:/root:/bin/bash
tom:x:1000:1000::/home/tom:/bin/bash
jerry:x:1001:1001::/home/jerry:/bin/bash
zhangsan:x:1002:1002::/home/zhangsan:/bin/bash
lisi:x:1003:1003::/home/lisi:/bin/bash

关系运算符
<    小于    x < y
<=    小于等于    x <= y
==    等于    x == y
!=    不等于    x != y
>=    大于等于    x >= y
>    大于    x > y

# awk 'NR>=2 && NR<=5{ print NR,$0}' 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

七、awk动作

第一部分: '{  }'         属于 "组合语句" 类型的动作,顾名思义,"组合语句" 类型的动作的作用就是将多个代码组合成代码块
第二部分:print $0    属于 "输出语句" 类型的动作,作用就是输出、打印信息

 例如:
# awk -F":" '{ print $1 }{ print $2 }' passwd   等价于  # awk -F":" '{ print $1 ; print $2 }' passwd

if判断

例如
# awk -F":" '{ if(NR==1){ print $1 } }' passwd
与awk的模式用法一样
# awk -F":" 'NR==1{ print $1 }' passwd

那如果是if else的写法呢
# awk -F":" '{ if($3 < 1000){ print $1,"系统用户" } else{print $1,"普通用户"} }' passwd 

那如果是if...else if...else 的写法呢
# cat score.txt 
姓名       成绩   
张三丰     88
李四年     72
王五毒     96
汤姆       58
杰瑞       66
ansible   150

# awk 'NR != 1{ if($2 > 100){ print $1,"逆天" } else if( $2 > 90 && $2 <=100 ){ print $1,"优秀" } else if( $2 >= 80 && $2 <90 ){ print $1,"良好" } else if( $2 >= 60 && $2 < 80 ){ print $1,"及格" } else{ print $1,"不及格" } }' score.txt
张三丰 良好
李四年 及格
王五毒 优秀
汤姆 不及格
杰瑞 及格
ansible 逆天

for循环

# awk 'BEGIN{ for( i=1;i<=6;i++ ){ print i } }'
1
2
3
4
5
6
# awk 'BEGIN{ for( i=1;i<=6;i++ ) { if(i==3){continue}; print i } }'
1
2
4
5
6
# awk 'BEGIN{ for( i=1;i<=6;i++ ) { if(i==3){break}; print i } }'
1
2
# awk 'BEGIN{ for( i=1;i<=6;i++ ) { if(i==3){exit}; print i } }'
1
2

while循环

# awk -v i=1 'BEGIN{ while(i<5) { print i;i++ } }'
# awk 'BEGIN{ i=1;while(i<5) { print i;i++ } }'
1
2
3
4

do…while循环

        它与while循环的不同之处在于,while循环只有当满足条件时才会执行对应语句,而do…while循环则是无论是否满足条件,都会先执行一遍do对应的代码,然后再判断是否满足while中对应的条件,满足条件,则执行do对应的代码,如果不满足条件,则不再执行do对应的代码


# awk 'BEGIN{ i=1;do{ print i;i++ } while(i<5)}'
# awk -v i=1 'BEGIN{ do{ print i;i++ } while(i<5)}'
1
2
3
4

next 跳过这一行

        awk是逐行对文本进行处理的,也就是说,awk会处理完当前行,再继续处理下一行,那么,当awk需要处理某一行文本的时候,我们能不能够告诉awk :"不用处理这一行了,直接从下一行开始处理就行了"

# head -3 passwd | awk -F":" '{ if( $3 == 0 ){ next } print $0 }'

八、内置函数(部分)

算数函数

rand函数、srand函数、int函数

可以使用rand函数生成随机数,但是使用rand函数时,需要配合srand函数,否则rand函数返回的值将一直不变
# awk 'BEGIN{ print rand() }'
0.237788
# awk 'BEGIN{ print rand() }'
0.237788

如果单纯的使用rand函数,生成的值是不变的,可以配合srand函数,生成一个大于0小于1的随机数
# awk 'BEGIN{srand();  print rand() }'
0.613538
# awk 'BEGIN{srand();  print rand() }'
0.415389
# awk 'BEGIN{srand();  print rand() }'
0.726018


生成的随机数都是小于1的小数,如果我们想要生成整数随机数,可以将随机数乘以100,然后截取整数部分,使用int函数可以截取整数部分的值
# awk 'BEGIN{srand();  print 1000*rand() }'
179.079
[root@ansible1 ~]# awk 'BEGIN{srand();  print int(1000*rand()) }'
604

字符串函数

gsub函数与sub函数、length函数

gsub函数
将passwd文件第一行的所有的列中的r换成R(默认所有的列$0)
# awk -F":" 'NR==1{ gsub("r","R") ; print $0 }' passwd 
Root:x:0:0:Root:/Root:/bin/bash

将passwd文件第一行的第一列中的r换成R
# awk -F":" 'NR==1{ gsub("r","R",$1) ; print $0 }' passwd 
Root x 0 0 root /root /bin/bash

gsub会替换指定范围内的所有符合条件的字符
sub函数只会替换指定范围内第一次匹配到的符合条件的字符
sub函数
# awk -F":" 'NR==1{ sub("r","R",$0) ; print $0 }' passwd 
Root:x:0:0:root:/root:/bin/bash


length函数
# awk -F":" 'NR==1{ print $1,length($1) }' passwd
root 4
找出字符长度为4的所有用户
# awk -F":" '{ if( length($1)==4 ){ print $1 } }' passwd
root
sync
halt
mail
dbus
sshd

九、printf

printf.png

print用于输出文本,printf的优势在于格式化输出文本
echo默认换行输出

# echo -e "hello \nansible \nopenstack \ndocker \nkvm"
hello 
ansible 
openstack 
docker 
kvm

# printf "hello \nansible \nopenstack \ndocker \nkvm\n"
hello 
ansible 
openstack 
docker 
kvm

# printf format  item1 item2 item3  ...
# printf "%s\n" hello ansible openstack docker kvm
hello
ansible
openstack
docker
kvm

# printf "(%s)" hello ansible openstack docker kvm

格式替换符
%s 字符串
%f 浮点格式(也就是我们概念中的float或者double)
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义。
%c ASCII字符。显示相对应参数的第一个字符
%d, %i 十进制整数
%o 不带正负号的八进制值
%u 不带正负号的十进制值
%x 不带正负号的十六进制值,使用a至f表示10至15
%X 不带正负号的十六进制值,使用A至F表示10至15
%% 表示”%”本身

转义字符
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f 换页(formfeed)
\n 换行
\r 回车(Carriage return)
\t 水平制表符
\v 垂直制表符
\\ 一个字面上的反斜杠字符,即”\”本身。
\ddd 表示1到3位数八进制值的字符,仅在格式字符串中有效
\0ddd 表示1到3位的八进制值字符

# printf "(%s)" hello ansible openstack docker kvm ;echo ""
(hello)(ansible)(openstack)(docker)(kvm)

# printf "%s %s %s\n" hello ansible openstack docker kvm ELK
hello ansible openstack
docker kvm ELK

# printf "%-8s %-10s %s\n" hello ansible openstack docker kvm ELK
hello    ansible    openstack
docker   kvm        ELK

 

printf1.png

结合BEGIN和END使用
awk -F":" 'BEGIN{ printf "%-20s\t %-20s\n", "用户名称","用户ID" }{ printf "%-20s\t %-20s\n",$1,$3 } END{ print "用户一共"NR"人" }'  passwd
用户名称                     用户ID                
root                     0                   
bin                      1                   
daemon                   2                   
adm                      3                   
lp                       4                   
用户一共5人