全面详解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