简单介绍
awk 是由 Alfred Aho 、Peter Weinberger、Brian Kernighan 这三个人创造的,所以 awk 名字是由这三个人姓氏的首字母组成。
awk 是一个报告生成器,拥有强大的文本格式化能力,我们可以利用 awk 命令,将一些文本处理成我们想要的样子,比如整理成 " 表格 " 的样子。
awk 是一个行编辑器;
awk 其实是一门编程语言,支持条件判断,数组,循环等功能,所以 awk 也是一个脚本语言解释器;
awk、sed、grep 并称为 Linux 三剑客,其中 grep 适合单纯的查找或匹配文本,sed 适合编辑匹配到的文本(特别是庞大的文本文件),而 awk 则长于对文本进行复杂的格式化处理;
语法
awk [options] 'pattern{action}' file1,file2...
awk [-F|-v] 'BEGIN{ commands } pattern{ commands } END{ commands }' file1,file2...
options
1) options省略时,以空白字符为分隔符;
2) -F 指定分隔符;
3) -v 设置变量;
变量分内置变量和自定义变量;
内置变量:
FS:输入分隔符,默认为空白字符;
OFS:输出分隔符,默认为空白字符;
RS:输入换行符,指定输入时的换行符;
ORS:输出换行符,输出时用指定符号代替换行符;
NF:当前行的字段的个数,即当前行被分割成了几列;
NR:行号,当前处理的文本行的行号;
FNR:各文件分别计数的行号,awk处理多文件时,分别计数各文件行号;
FILENAME:当前文件名;
ARGV:数组,保存的是命令行所给定的各参数;
ARGC:命令行参数的个数;
自定义变量可以在 options 中指定,也可以在 '{ }' 程序段中指定,区别是前者可以调用 shell 中的变量,然后传参给 '{ }' 程序段;
4) --posix 当 patter 部分使用正则模式的次数匹配时 {m,n},搭配使用该选项;
pattern
pattern,模式,也叫条件,只要被 pattern 匹配到的文本行,都执行 action 部分的动作。
1) pattern 省略时,表示空模式,空模式会匹配文本当中的每一行,对每一行都执行 action 部分的动作;
2) BEGIN/END 模式
BEGIN{ commands } # 处理文本之前执行的动作;
END{ commands } # 文本处理之后执行的动作;
3) 关系运算模式
> < == != >= <= ~ !~
~ 和 !~ 搭配正则表达式进行模式匹配,eg:
[root@Python ~]# df
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/mapper/centos-root 17811456 8719660 9091796 49% /
devtmpfs 914752 0 914752 0% /dev
tmpfs 931624 0 931624 0% /dev/shm
tmpfs 931624 10092 921532 2% /run
tmpfs 931624 0 931624 0% /sys/fs/cgroup
/dev/sda1 1038336 165968 872368 16% /boot
tmpfs 186328 0 186328 0% /run/user/0
[root@Python ~]# df | awk '$6~/^\/run/{print $0}'
tmpfs 931624 10092 921532 2% /run
tmpfs 186328 0 186328 0% /run/user/0
4) 正则模式
pattern 部分正则模式匹配要放入两个斜线中,语法如下:
awk [options] '/正则表达式/{action}' file
注1:如果正则表达式中包含 '/' 要使用 '\' 进行转义;
注2:awk 默认使用扩展正则表达式;
注3:使用 {m,n} 进行次数匹配时需要使用 --posix 选项,不然会报错;
5) 行范围模式
语法:
awk [options] '/正则1/,/正则2/{action}' file
action
action,动作,分为两大类,输出语句和控制语句;
1、输出语句
1) action 省略
如若省略 action,则表示被 pattern 匹配到的行都打印出来,及默认{ print $0 };
2) print 简单打印
可打印字符串,也可将 pattern 匹配到的行打印至屏幕,同时可结合位置变量打印我们需要的字段至屏幕;
常用位置变量:
$0 $1...$n $NF $(NF-1)
$NF表示当前行的最后一个字段;
3) printf 格式化打印
printf() 是C语言中的一个函数,linux 的 printf 命令模仿了该函数,可以按照我们指定的格式输出文本;awk 工具 嵌入了 printf 命令,让 action 部分可以格式化打印文本;
printf 核心功能为 格式替换符和转义字符;
格式替换符:
- 左对齐
Width 域的步长,用0表示0步长
.prec 最大字符串长度,或小数点右面的位数
%c ASCII字符
%d 整型
%e 科学计数法
%f 浮点型 #使用小数点后2位%.2f (用于除法后)
%g awk决定哪种浮点数转换e或者f
%o 八进制
%s 字符串
%x 十六进制
转义字符:
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\f 换页
\b backspace 删除前面一个字符,前提是\b后面要存在字符
\c 不显示输出结果中的换行字符
举个栗子:
[root@syztoo ~]# awk -F: 'BEGIN{ printf "%-10s\t%s\n" , "用户名称","用户ID" } { printf "%-10s\t%s\n" , $1,$3 }' /etc/passwd
用户名称 用户ID
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
systemd-network 192
dbus 81
polkitd 999
sshd 74
postfix 89
chrony 998
ntp 38
tcpdump 72
nscd 28
nginx 997
mysql 27
dockerroot 996
tss 59
syztoo 1000
2、控制语句
1) if
语法:
awk [options] 'pattern{if(条件){输出语句}}' file
2) if...else
语法:
awk [options] 'pattern{if(条件){输出语句}else{输出语句}}' file
3) if...else if...else
语法:
awk [options] 'pattern{if(条件){输出语句}else if(条件){输出语句}else{输出语句}}' file
4) for
for循环语法格式1:
for(初始化; 布尔表达式; 更新) { 代码语句 }
栗子 :
[root@syztoo ~]# awk 'BEGIN{ for(i=1; i<=5; i++) { print i} }'
1
2
3
4
5
for循环语法格式2:
for( 变量 in 数组 ) { 代码语句 }
栗子:
[root@syztoo ~]# awk 'BEGIN{arr[1]="dog";arr[2]="cat";arr[3]="pig"; for(i in arr){ print arr[i] }}'
dog
cat
pig
5) while
语法:
while( 布尔表达式 ) { 代码语句 }
栗子:
[root@syztoo ~]# awk -v n=1 'BEGIN{ while (n<=5) {print n; n++}}'
1
2
3
4
5
[root@syztoo ~]# awk 'BEGIN{ n=2 ; while (n<=5) {print n; n++}}'
2
3
4
5
6) do...while
语法:
do { 代码语句 } while( 条件 )
7)break,continue
awk 与其他编程语言一样,在 for 或者 while 循环中,也可以使用 break 和 continue 来跳出循环,break 跳出整个循环,continue 跳出本次循环。
栗子:
[root@syztoo ~]# awk 'BEGIN{ n=1 ; while (n<=10) { if (n>=5) { break }; print n; n++ }}'
1
2
3
4
[root@syztoo ~]# awk 'BEGIN{ n=1; while(n<=10) { n++; if(n%2==1) { continue }; print n }}'
2
4
6
8
10
8)exit
在 shell 中,exit 表示退出当前脚本,在 awk 中,也可使用 exit,表示退出整个 awk 命令;
栗子:
[root@syztoo ~]# awk 'BEGIN{ print 1 ; exit ; print 2 ; print 3 }'
1
数组
awk 中的数组不需要向 java 一样提前进行声明。
栗子:
[root@syztoo ~]# awk 'BEGIN{animal[1]="dog";animal[2]="cat";animal[3]="pig";for(i in animal){print i,animal[i]}}'
1 dog
2 cat
3 pig
awk 创造数组时,除了手动创建,也可直接使用系统中已经定义好的数组,通过 -v 变量传入即可。
还可以使用 awk 内置 split 函数,将一段文本按指定分隔符进行分割,然后将分割后的字段保存为一个数组,只不过该数组的索引不是从0开始,而是从1开始;
举个栗子:
[root@syztoo ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/java/bin:/usr/local/java/jre/bin:/usr/local/maven/bin:/usr/local/python3/bin:/root/bin
[root@syztoo ~]# awk -v x=$PATH 'BEGIN{split(x, path, ":"); for(i in path) {print i,path[i]}}'
4 /usr/bin
5 /usr/local/java/bin
6 /usr/local/java/jre/bin
7 /usr/local/maven/bin
8 /usr/local/python3/bin
9 /root/bin
1 /usr/local/sbin
2 /usr/local/bin
3 /usr/sbin
再举个实际应用的栗子,统计文本中某个 IP 地址出现的次数;
[root@syztoo ~]# cat test.txt
192.168.1.1
192.168.1.4
192.168.1.2
192.168.1.4
192.168.1.2
192.168.1.3
192.168.1.3
192.168.1.3
[root@syztoo ~]# awk '{ip[$1]++} END{for(i in ip){print i,ip[i]}}' test.txt
192.168.1.1 1
192.168.1.2 2
192.168.1.3 3
192.168.1.4 2
内置函数
awk 中的内置函数大致分为算术函数、字符串函数、时间函数和其他函数等。
1、算术函数
最常用的有 rand 、srand 、int 函数。
[root@syztoo ~]# awk 'BEGIN{ print rand() }'
0.237788
[root@syztoo ~]# awk 'BEGIN{ print rand() }'
0.237788
[root@syztoo ~]# awk 'BEGIN{ print rand() }'
0.237788
rand 函数返回一个 0 到 1 之间的随机数,但使用 rand 函数时,需要配合 srand 函数,否则就会出现上面的执行结果。
[root@syztoo ~]# awk 'BEGIN{ srand(); print rand() }'
0.863585
[root@syztoo ~]# awk 'BEGIN{ srand(); print rand() }'
0.298565
[root@syztoo ~]# awk 'BEGIN{ srand(); print rand() }'
0.89975
如果想生成一个 0 到 10 之间的整数,可以将随机数 * 10 然后用 int 函数取整即可。
[root@syztoo ~]# awk 'BEGIN{ srand(); print int(rand()*10) }'
9
[root@syztoo ~]# awk 'BEGIN{ srand(); print int(rand()*10) }'
8
[root@syztoo ~]# awk 'BEGIN{ srand(); print int(rand()*10) }'
5
2、字符串函数
常用的字符串函数有 gsub、sub、length、index、split 函数等。
[root@syztoo ~]# cat a.txt
hell0
i am syzt00
what are y0u d0ing
[root@syztoo ~]# awk '{ gsub("0","o",$1); print $0 }' a.txt
hello
i am syzt00
what are y0u d0ing
[root@syztoo ~]# awk '{ gsub("0","o",$0); print $0 }' a.txt
hello
i am syztoo
what are you doing
[root@syztoo ~]# awk '{ gsub("0","o"); print $0 }' a.txt
hello
i am syztoo
what are you doing
gsub 函数接收三个参数,第一个是要替换的字符(可以使用正则),第二个要替换成什么字符,第三个是选择要替换的列(省略时,默认为 $0,全部替换)。
sub 函数与 gsub 函数类似,区别在于 gsub 函数会替换指定范围内所有符合条件的字符,而 sub 函数只会替换指定范围内第一次匹配到字符。
[root@syztoo ~]# cat a.txt
hell0
i am syzt00
what are y0u d0ing
[root@syztoo ~]# awk '{ gsub("0","o"); print $0 }' a.txt
hello
i am syztoo
what are you doing
[root@syztoo ~]# awk '{ sub("0","o"); print $0 }' a.txt
hello
i am syzto0
what are you d0ing
length 函数可以获取字符串长度。
[root@syztoo ~]# awk '{ print $1,length($1) }' a.txt
hell0 5
i 1
what 4
index 函数返回字符串的位置。
[root@syztoo ~]# cat a.txt
hell0
i am syzt00
what are y0u d0ing
[root@syztoo ~]# awk '{ print index($0,"0") }' a.txt
5
10
11
[root@syztoo ~]# awk '{ print index($0,"00") }' a.txt
0
10
0
[root@syztoo ~]# awk '{ print index($3,"00") }' a.txt
0
5
0
index 接受两个参数,第一个参数指定索引范围(表示整行时,$0 不能省略),第二个参数指定要查找的字符串。匹配机制是,没有匹配到返回 0,第一次匹配到就返回索引位置(不会再往后面匹配)。
split 函数可将字符串分隔,然后保存为数组。
[root@syztoo ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/java/bin:/usr/local/java/jre/bin:/usr/local/maven/bin:/usr/local/python3/bin:/root/bin
[root@syztoo ~]# awk -v x=$PATH 'BEGIN{split(x, path, ":"); for(i in path) {print i,path[i]}}'
4 /usr/bin
5 /usr/local/java/bin
6 /usr/local/java/jre/bin
7 /usr/local/maven/bin
8 /usr/local/python3/bin
9 /root/bin
1 /usr/local/sbin
2 /usr/local/bin
3 /usr/sbin
注意 split 函数分隔后的数组索引是从 1 开始,split 函数也有返回内容,返回分隔后的数组长度。
[root@syztoo ~]# awk -v x=$PATH 'BEGIN{ print split(x, path, ":") }'
9
三元运算
格式:
condition ? result 1 : result 2
如果条件成立,执行 result 1 ,否则执行 result 2
栗子:
[root@syztoo ~]# awk -F: '{ $3 < 1000? a++: b++ } END{ print a,b }' /etc/passwd
26 1
打印奇偶行
[root@syztoo ~]# cat a.txt
1 hello
2 i am syztoo
3 what are you doing
4 hi syztoo
5 i just got up
6 i will play basketball later
7 would you like to join me
8 ok
9 see you later
打印奇数行:
[root@syztoo ~]# awk 'a=!a' a.txt
1 hello
3 what are you doing
5 i just got up
7 would you like to join me
9 see you later
打印偶数行:
[root@syztoo ~]# awk '!(a=!a)' a.txt
2 i am syztoo
4 hi syztoo
6 i will play basketball later
8 ok