文本三剑客之 awk

文本三剑客之 awk

Linux文本三剑客: grep,sed,awk。grep 用于过滤文本,sed 用于行编辑, awk 能对文本每行进行分段处理,并格式化输出。

awk 源于其三位创始人Alfred Aho, Peter Weinberger 和 Brian Kernighan 的姓名首字母。是一种解释型过程式编程语言,awk支持数组,条件判断,循环,函数等通用的编程功能。

Linux系统中使用的是 awk 的一种变种 gawk,如下所示,awk 就是 gawk 的一个软链接。

[root@localhost ~]# ll /usr/bin/awk
lrwxrwxrwx. 1 root root 4 Aug 18 16:25 /usr/bin/awk -> gawk

AWK 工作流

Alt text
如上图所示,awk 处理流程:

  • 执行BEGIN子句 : BEGIN子句为awk程序开始时执行,且只执行一次
  • 读入新行:从标准输入,文件或管道读入一行内容
  • 处理模式匹配的行:对读入的行通过PATTERN模式匹配,对匹配到的行进行处理
  • 判断是否为最后一行:若不是最后一行将循环读入新行进行处理,若是最后一行执行END子句
  • 执行END子句:当所有行处理完成后执行END子句,END子句只执行一次

注意:AWK 程序中 BEGIN{ ACTION },PATTERN{ ACTION },END{ ACTION } 这三个子句包含任意一个或多个均可以


AWK 语法

  1. AWK 运行方式有三种
    • 第一种 AWK 命令行
      awk [OPTIONS] 'AWK_PROGRAM' INPUT_FILE...
      在 shell 命令行编写执行 AWK 程序
* 第二种 AWK 程序文件
`awk [OPTIONS] -f SCRIPT_AWK INPUT_FILE...`
将 AWK 命令写入到脚本文件中,然后通过选项 -f 调用脚本文件执行 AWK 命令
* 第三种 AWK 脚本
`AWK_FILE [OPTIONS] INPUT_FILE...`
将 AWK 程序写入文件中,在文件的首行指定 AWK命令行解释器 #!/bin/awk -f,然后给 AWK 执行权限,就可以直接执行AWK 程序,不需向前两种那样输命令 `awk`
  1. AWK 程序样式
awk [OPTIONS] 'BEGIN{ACTION} pattern{ACTION} END{ACTION}' INPUT_FILES
awk [OPTIONS] 'BEGIN{ACTION} pattern{ACTION} END{ACTION}' << EOF`
STOUT | awk [OPTIONS] 'BEGIN{ACTION} pattern{ACTION} END{ACTION}'`
awk [OPTIONS] -f AWK_SCRIPTS INPUT_FILES
AWK_FILE [OPTIONS] INPUT_FILES

举例

[root@localhost ~]# awk -F ":" 'BEGIN{printf "%10s %10s\n","USER","UID";print "------------------------"} /bash$/{printf "%10s %10s\n",$1,$3} END{print "------------------------";print "end file"}' /etc/passwd
USER UID
------------------------
root 0
zb201 1000
------------------------
end file

AWK 选项

  • -F 'FILED_SEPARATOR' 文本输入每行各字段间的分隔符
    分隔符可以指定多个 如使用空白或冒号 “:” 作为分隔符 -F '[:[:space]]'+
  • -v VAR=VALUE 定义变量

打印输出:print 和 printf

  1. print
    使用 print 用于打印输出字符串,变量,数字运算表达式等,使用上请注意以下细节
    • 打印字符串时,将字符串放入到引号内,如:print "STRING"
    • 逗号,在 print 中用作分隔符,输出时显示为空格,如:print $1,$2
    • 变量,数字, 数字运算表达式不需要包含在引号内,如:print 2+3*4
    • print 打印的内容末尾自动换行
    • awk '!0', awk '{print}', awk '{print $0}' 功能相同

举例

[root@localhost ~]# awk 'BEGIN{print "Hello everyone!!"}'
Hello everyone!!
[root@localhost ~]# awk 'BEGIN{math=100;english=90;print"sum="math+english,"avg="(math+english)/2}'
sum=190 avg=95
  1. printf
    printf "FORMAT",item1,tiem2 ...
    格式化打印输出内容
    • printf 打印不自动换行,若需换行需加\n,如:printf "STRING \n"
    • FORMAT 格式符,与 item 一一对应
|格式符|含义|举例|
|:--|:--|:--|
|%c|打印字符的ASCII码|printf "%s",63|
|%d, %i|打印整数|printf "%d",10|
|%f|打印浮点数|print "%f",3.2|
|%e, %E|以科学计数法显示数字|printf "%e",2000.33|
|%s|打印字符串|print "%s","good"|
|%#[.#]{c,f,s,d}|第一个#指显示的宽度,第二个#指浮点数小数位数|printf "%5.2f",3.14159|
|%-{c,f,s,d}|左对齐输出的字符串,默认是右对齐|printf "%-15s","nice"|

举例

# 打印磁盘分区利用率
[root@localhost ~]# df | awk '{printf "%-15s %s\n",$1,$5}'
Filesystem Use%
/dev/sda2 3%
devtmpfs 0%
tmpfs 0%
tmpfs 2%
tmpfs 0%
/dev/sda5 1%
/dev/sda1 13%
tmpfs 0%
/dev/sr0 100%
# 计算圆的面积
[root@localhost ~]# awk 'BEGIN{PI=3.14;r=5;printf "Area=%6.2f\n",PI*r^2}'
Area= 78.50

AWK 中的变量

  • 保存各字段的变量
    AWK中使用$NUM保存各字段的变量
    $0 指 AWK 命令本身
    $1, $2, $3 ... 分别代表第一个,第二个,第三个 字段的变量

  • 内置变量
FS : 输入文件字段分隔符,类似于 -F 选项
OFS :输出字段分隔符
RS : 输入记录分隔符
ORS :输出记录分隔符
NF :每个记录的字段数,如:$NF,$(NF-1)
NR :行编号/记录编号
FNR :文件行编号,有多个输入文件时,NR是把所有文件合并为一个进行编号,而FNR是分别对每个文件编号
FILENAME: 文件名
ARGC :文件参数个数,awk是其中一个参数
ARGV :参数数组

举例

# 指定字段的分隔符为 ":"
[root@localhost ~]# awk -v FS=':' '/^root/{print $1,$3,$7}' /etc/passwd
root 0 /bin/bash
# 输出字段分隔符字指定为 ";"
[root@localhost ~]# awk -v FS=':' -v OFS=";" '/^root/{print $1,$3,$7}' /etc/passwd
root;0;/bin/bash
# 默认行的分隔符为回车 "\n" RS 可以指定其它字符作为输入记录(或行)的分隔符
[root@localhost ~]# awk '{print NR,$0}' file
1 Hello everyone;Welcom to
2 my blog;Some thing
3 so beautiful
[root@localhost ~]# awk -v RS=";" '{print NR,$0}' file
1 Hello everyone
2 Welcom to
my blog
3 Some thing
so beautiful
# 默认记录输出的分隔符为换行符 "\n", 可以使用 OFS 指定其它符号作为记录的分隔符
awk 'BEGIN{ORS="---"} {print NR,$0}' file
1 Hello everyone;Welcom to ---2 my blog;Some thing---3 so beautiful---
# NF指每行字段的数量,$NF 则表示最后一个字段的内容,倒数第二个字段为 ${NF-1}
[root@localhost ~]# awk '{print NF,$NF}' file
3 to
3 thing
2 beautiful
# ARGC 指参数的个数,awk也是其中一个参数,而ARGV存储参数的属组
[root@localhost ~]# awk 'BEGIN{print ARGC,ARGV[0],ARGV[1],ARGV[ARGC-1]}' /etc/fstab /etc/passwd
3 awk /etc/fstab /etc/passwd
  • 自定义变量
    -v VAR=VALUE
    我们可以使用 -v 选项自定义变量,然后就可以引用SHELL中的变量,如 -v VAR="$HOME",需要注意的是 AWK 中处理各行时共享相同的变量。

举例

# 统计/etc/passwd的行数
[root@localhost ~]# num=0;awk -v count="$num" '{count++} END{print count}' /etc/passwd
24

AWK 操作符

  • 算术
加:+,减:-, 乘:*, 除:/,幂:^,取余:%
  • 赋值
+=, -=, *=, /=, ^=, ++, --
  • 比较
>, >=, <, <=, ==, !=
  • 模式匹配
~, !~
# 打印/etc/passwd 中包含 root 字符串的行
[root@localhost ~]# awk '$0 ~ /root/{print $0}' /etc/passwd
root❌0:0:root:/root:/bin/bash
operator❌11:0:operator:/root:/sbin/nologin
# 打印/etc/passwd 中不包含 nologin 字符串的行
[root@localhost ~]# awk '$0 !~ /nologin/{print $0}' /etc/passwd
root❌0:0:root:/root:/bin/bash
sync❌5:0:sync:/sbin:/bin/sync
shutdown❌6:0:shutdown:/sbin:/sbin/shutdown
halt❌7:0:halt:/sbin:/sbin/halt
zb201❌1000:1000:zb201:/home/zb201:/bin/bash
  • 逻辑操作符
    && :逻辑与
    || :逻辑或
    ! :逻辑非
[root@localhost ~]# awk '!($0 ~ /nologin/) && $0 ~ /bash/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
zb201:x:1000:1000:zb201:/home/zb201:/bin/bash

AWK 中的模式 PATTERN

PATTERN 模式用于过滤行,只有匹配的行才读入进行后续处理。PATTERN模式有以下几种:

  • 正则表达式:
    使用正则表达式 /REGULAR_EXPRESSION/ 来过滤行,正则表达式写在正斜线内,使用样式:awk [OPTIONS] '/REGULAR_EXPRESSION/{ ACTION }' INPUTTILES。功能等同于 $0 ~ /REGULAR_EXPRESSION/

举例

取出磁盘分区利用率大于10%的磁盘
[root@localhost ~]# df | awk -F "[[:space:]%]+" '/^\/dev\/sd/{if($5>10){print $1,$5}}'
/dev/sda1 13
  • 关系表达式:
    是否对行进行处理,除了可以用正则表达式匹配,还可以使用关系表达式判断,表达式的值为真则处理,否则不处理。
    • 假值:表达式的值等于 0 或表达式的值是空值
    • 真值:表达式的值不等于0 且 表达式的值不是空值

举例

[root@localhost ~]# tail -n 2 /etc/passwd | awk -F ":" '!0{print $1,$3}'
apache 48
mysql 27
[root@localhost ~]# awk -F ":" '$3>=1000{print $1,$3}' /etc/passwd
zb201 1000
  • 行范围
    可以使用正则表达式匹配需处理的行范围/pat1/,/pat2/。AWK中取行的范围,不能直接输入行编号,但我们可以通过内置变量NR配合逻辑表达式来实现取具体的行范围

举例

[root@localhost ~]# awk -F ":" '/^root/,/^bin/{print $1,$3}' /etc/passwd
root 0
bin 1
[root@localhost ~]# awk -F ":" 'NR>=5 && NR<=8{print $1,$3}' /etc/passwd
lp 4
sync 5
shutdown 6
halt 7
  • BEGIN/END
    BEGIN 和 END 子句也可以当做一种特殊的模式来理解,其含义为:
    • BEGIN : AWK 程序开始时执行,并且只执行一次
    • END : AWK 程序最后执行,并且只执行一次

举例

[root@zhubiao ~]# awk 'BEGIN{printf "%-15s %-15s %-15s\n","Device","MountPoint","FileSystem"} {printf "%-15s %-15s %-15s\n",$1,$2,$3} END{print "End File"}' /etc/mtab
Device MountPoint FileSystem
/dev/sda2 / ext4
proc /proc proc
sysfs /sys sysfs
devpts /dev/pts devpts
tmpfs /dev/shm tmpfs
/dev/sda3 /app ext4
/dev/sda1 /boot ext4
none /proc/sys/fs/binfmt_misc binfmt_misc
/dev/sr0 /mnt/cd iso9660
End File

条件判断语句

AWK 中可以通过条件判断来执行不同分支的程序,其中条件判断语句有 三目表达式,if 语句,switch 语句。

  • 三目表达式
    CONDITION?STATEMENTS1:STATEMENTS2
    CONDITION 为判断条件,条件为真则执行 STATEMENTS1分支语句,条件为假则执行 STATEMENTS2分支语句。

举例

[root@zhubiao ~]# awk 'BEGIN{score=80;score>60?result="Pass":result="Not pass";print result}'
Pass
  • if 语句
# 语法:
if(CONDITION)
{
STATEMENTS
}
else if(CONDITION)
{
STATEMENTS
}
else
{
STATEMENTS
}

举例:scores.txt 文件中存储了各位同学的成绩及性别,要求分别统计男女同学的总分和平均分

# 成绩单
[root@zhubiao ~]# cat scores.txt
mayun 90 male
zhangzetian 88 female
liqiangdong 90 male
mahuateng 86 male
dongmingzhu 87 female
liyanhong 98 male
# 实现功能的 AWK 脚本文件
[root@zhubiao ~]# cat script.awk
BEGIN{
msum=0
mcount=0
fsum=0
fcount=0
}
{
if($3 == "male")
{
msum+=$2
mcount++
}
else if($3 == "female")
{
fsum+=$2
fcount++
}
}
END{
mavg=msum/mcount
favg=fsum/fcount
printf "msum=%5d \t mavg=%6.2f\n",msum,mavg
printf "fsum=%5d \t favg=%6.2f\n",fsum,favg
}
# 执行结果
[root@zhubiao ~]# awk -f script.awk scores.txt
msum= 364 mavg= 91.00
fsum= 175 favg= 87.50
  • switch 语句

语法

switch(EXPRESION) {
case VALUE or /REGEXP/:
STATEMENTS
case VALUE or /REGEXP/:
STATEMENTS
default:
STATEMENTS
}

循环语句

AWK 中可以使用循环语句执行重复的代码,AWK中的循环结构语句有 while 语句,do while 语句,for 语句

  • whlie 循环语句
while ( CONDITION ) {
STATEMENTS
}

举例

# 求1100之和
[root@zhubiao ~]# awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print sum}'
5050
  • do while 循环语句
    do…while 功能与 while 类似,区别在于 do…while 执行一次才判断条件是否为真。
do ( CONDITION ) {
STATEMENTS
} while ( CONDITION )
  • for 循环语句
# for语句 语法一
for(ASSIGNMENT;CONDITION;EXPRESSION) {
STATMENTS
}
# for语句 语法二
for(VAR in ARRAY_NAME) {
STATMENTS
}

举例

# 显示/etc/fstab 中根挂载点各字段字符串的长度
[root@zhubiao ~]# awk '$2 == "/"{for(i=1;i<=NF;i++){print $i,":",length($i)}}' /etc/fstab
UUID=854ccdf1-ef95-4b32-931d-9f915c8ca632 : 41
/ : 1
ext4 : 4
defaults : 8
1 : 1
1 : 1

awk控制语句

  • break[n]
    当遇到 break 语句则退出当前循环,执行循环后面的程序,n 为退出循环的层次
  • continue[n]
    当遇到 continue 语句则不执行本次循环语句中 continue 后面的程序,执行下一个循环
  • next
    当遇到 next 语句时,则结束处理本行,直接跳转去处理下一行

AWK 数组
ARRAY[INDEX]
AWK 中的数组为关联数组,即数组中的索引和值类似键值对的形式。

  • 索引:INDEX
    索引可以为任何字符串,表达式,变量。字符串要使用双引号括起来。
  • 对数组的赋值形式为 ARRAY[INDEX]=VALUE

举例 如下所示,分别计算scores.txt成绩单中男女同学的总分和平均分

# 成绩单
[root@zhubiao ~]# cat scores.txt
mayun 90 male
zhangzetian 88 female
liqiangdong 90 male
mahuateng 86 male
dongmingzhu 87 female
liyanhong 98 male
# 将 AWK 命令写到脚本 script.awk中,使用 -f 选项调用脚本执行
[root@zhubiao ~]# cat script.awk
{
sum[$3]+=$2
count[$3]++
}
END{
for(gender in sum){
printf "%8s: sum=%4d \t avg=%6.2f\n",gender,sum[gender],sum[gender]/count[gender]
}
}
# 执行结果
[root@zhubiao ~]# awk -f script.awk scores.txt
female: sum= 175 avg= 87.50
male: sum= 364 avg= 91.00

函数 function

AWK 中的函数有两种,一种是AWK内部自定义的内置函数,直接调用即可使用,另一种是我们自己定义使用。

  • 内置函数
srand(), rand() :生成随机数
int() :将其他数据类型转换为整数
length() :取字符串的长度
sub(r,s,t) :将t中第一个被r匹配的字符替换为s
gsub(r,s,t) :将t中所有被r匹配的字符替换为s
split(s,array,d) :以d为分隔符分割字符串s,并将分割的各字段保存到数组array中,数组的索引值为:1,2,3...
  • 自定义函数
定义
function F_NAME(PARAMATER1,PARAMATER2 ...) {
STATEMENTS
returen EXPRESION
}
调用
无返回值:F_NAME(ARG1,ARG2...)
有返回值:VAR=F_NAME(ARG1,ARG2...)

举例

# 此处直接将 AWk 程序写入脚本中, 然后执行脚本
# AWK 脚本程序
[root@zhubiao ~]# cat file.awk
#!/bin/awk -f
function max(num1,num2){
num1>num2?max_num=num1:max_num=num2
return max_num
}
BEGIN{
print "The max number is: " max(number1,number2)
}
# 给 AWK 脚本执行权限
[root@zhubiao ~]# chmod +x file.awk
# 执行脚本
[root@zhubiao ~]# ./file.awk -v number1=10 -v number2=20
The max number is: 20

调用系统命令
system("SHELL_COMMAND")
在 AWK 程序中可以使用函数 system 调用 shell 中的系统命令,sehll 命令用双引号括起来

[root@zhubiao ~]# awk 'BEGIN{system("echo a sunny day")}'
a sunny day