Linux Shell编程第4章——sed和awk

sed和awkLinux/UNIX系统中两大文本处理工具。sed是流编辑器(stream editor),是一个将一系列编辑命令作用于一批文件的理想工具。awk因其三位缔造者的名字而命名(Aho、Weinberger和Kernighan),是一种能够对结构化数据进行操作,并产生格式化报表的编程语言。

sed命令基本用法

sed从文本的一个文本行或标准输入中读取数据,将其复制到缓冲区,然后读取命令行或脚本的第一个命令,对此命令要求的行进行编辑,重复此过程直到命令行或脚本中的所有命令都执行完毕。

sed只是对缓冲区中原始文件的副本进行编辑,并不编辑原始的文件。如果需要保存改动的内容,需要使用-i命令选项或者将输出重定向到另一个文件。

一般而言,调用sed有以下三种方式:

  • 在Shell命令行输入命令调用sed:sed [选项] '命令' 输入文件
  • 编辑脚本,然后调用:sed [选项] -f sed脚本 输入文件
  • 编辑脚本,设置脚本可执行,然后执行该脚本:./sed脚本 输入文件

sed常用命令选项及其意义如下:

选项 意义
-n 取消自动输出所有行,只有经过sed特殊处理的那一行(或者动作)才会被列出来(sed默认会输出所有行)
-e 表示将下一个字符串解析为sed编辑命令,如果只有一个编辑命令,-e选项可以省略
-f 表示调用脚本文件
-i 表示直接修改文件内容

sed命令通常由定位文本行和sed编辑命令两部分组成,sed编辑命令对定位文本行进行各种处理,sed提供以下两种方式定位文本。

  • 使用行号,指定一行,或指定行号范围
  • 使用正则表达式

这两种方式也可混用,sed命令定位文本的方法如下:

选项 意义
x x为指定行号
x,y 指定从x到y的行号范围
/pattern/ 匹配模式的行
/pattern/pattern/ 匹配两个模式的行
/pattern/,x 从与pattern的匹配行到行号为x之间的行
x,/pattern/ 从行号为x的行到与pattern的匹配行之间的行
x,y! 匹配除了从行号为x到行号为y之间的所有行

sed提供了丰富的编辑命令对文本进行处理,常用的sed编辑命令如下:

选项 意义
p 输出匹配行
= 输出行号
a\ 在定位行号之后追加文本信息
i\ 在定位行号之前插入文本信息
d 删除定位行
c\ 修改定位行
s 替换对应文本
r 从另一个文件中读文本
w 将文本写入到一个文件
y 变换字符
q 第一个模式匹配完成后退出
l 显示与八进制ASCII码等价的控制字符
{} 在定位行执行命令组
n 读取下一个命令行,用下个命令处理新的行
h 将模式缓冲区的文本复制到保持缓冲区
H 将模式缓冲区的文本追加到保持缓冲区
x 将模式缓冲区和保持缓冲区的内容交换
g 将保持缓冲区的文本复制到模式缓冲区
G 将保持缓冲区的文本追加到模式缓冲区

sed命令实例

首先新建名为input的文件,以此为例。

This is a Certificate Request file:

It should be mailed to zawu@seu.edu.cn

============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

命令选项

#sed -n用法
#带-n选项,只打印第1行
$ sed -n '1p' input
This is a Certificate Request file:
#不带-n选项,不仅打印第1行,还打印全部内容
$ sed '1p' input
This is a Certificate Request file:
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem #sed命令打印范围行
#打印第3~6行
$ sed -n '3,6p' input
It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject:
#打印匹配模式行
$ sed -n '/certificate/p' input
The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. #sed -e用法
#打印匹配行号
$ sed -n '/Certificate/=' input
1
6
#打印匹配行号及内容,使用p、=两个编辑命令
$ sed -n -e '/Certificate/p' -e '/Certificate/=' input
This is a Certificate Request file:
1
Certificate Subject:
6
#{}的使用可以使sed支持带多个编辑命令,上面那条命令,等同于sed -n '/Certificate/{p;=}' input

利用;间隔多条编辑命令等价于用-e选项后接多条编辑命令。例如,下面两条命令是等价的:

sed -n -e '/Certificate/p' -e '/Certificate/=' input
sed -n '/Certificate/p; /Certificate/=' input

在Bourne Shell中,还有一种sed多编辑命令的用法,输入sed',按下Enter键,Shell将出现>二级命令提示符,在此可以输入多条编辑命令,最后一条编辑命令加上',然后输入文件名称结束sed命令。

-f选项只在调用sed脚本时使用。sed脚本的第1行与bash Shell脚本类似,以#!开头,后面是解释器路径。可用which sed命令获得sed解释器路径。如下:

$ which sed
/bin/sed

文本定位

#匹配.字符所在行(需要转义)
$ sed -n '/\./p' input
It should be mailed to zawu@seu.edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus
The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file.
/home/alloy/linuxshell/CH02/usercert.pem #使用$符号匹配最后一行(在sed命令中$表示最后一行)
$ sed -n '$p' input
/home/alloy/linuxshell/CH02/usercert.pem #匹配以bus结尾的字符串
$ sed -n '/.*bus/p' input
/O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus #用!符号匹配不在某一范围的行
$ sed -n '2,10!p' input #匹配不在第2~10之间的行
This is a Certificate Request file:
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem #使用行号与模式匹配限定行范围
# sed -n '/seugrid/,$p' input #打印seugrid的匹配行到最后一行之间的行
/O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem $ sed -n '3,/seugrid/p' input #打印第3行到与seugrid匹配的行
It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus

编辑命令

1.追加文本

#在匹配file:的行后追加文本
$ sed '/file:/a\We append a new line.' input
This is a Certificate Request file:
We append a new line. It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

通过sed追加文本的例子接下来说明编写和调用sed脚本。创建一个名为append.sed的文件,内容如下:

#!/bin/sed -f

/file:/a\
We append a new line.\
We append another line.

-f表示正在调用脚本,若无此选项,赋予该文件可执行权限后执行会报错。追加文本时如果追加的文本有多行,需要用“\”换行。

$ chmod u+x append.sed      #赋予可执行权限
$ ./append.sed input #执行脚本
This is a Certificate Request file:
We append a new line.
We append another line. It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

2.插入文本,新建一个文件insert.sed,内容如下:

#!/bin/sed -f

/file:/i\
We insert a new line.
$ chmod u+x insert.sed
$ ./insert.sed input
We insert a new line.
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

3.修改文本,新建一个文件modify.sed,内容如下:

#!/bin/sed -f

/file:/c\
We modify this line.
$ chmod u+x modify.sed
$ ./modify.sed input
We modify this line. It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

4.删除文本

#删除第一行文本
$ sed '1d' input It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem #删除最后一行文本
$ sed '$d' input
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. #删除第1~10行
$ sed '1,10d' input
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

5.替换文本

替换文本与修改文本的区别在于:替换文本替换的是一个字符串,而修改文本是对整行进行修改。sed替换文本格式如下:

s/被替换的字符串/新字符串/[替换选项]

sed替换选项及其意义如下:

选项 意义
g 全局替换,替换所有匹配的字符串(默认情况下替换一行的第一个匹配字符串,之后的不替换)
p 与-n选项结合,只打印替换行
w 文件名 表示将输出定向到一个文件
#sed替换选项p的用法
#默认情况下,打印全部输入文件内容
$ sed 's/Certificate/CERTIFICATE/' input
This is a CERTIFICATE Request file: It should be mailed to zawu@seu.edu.cn ============================================================
CERTIFICATE Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem #-n和p选项结合使用,只打印替换行
$ sed -n 's/Certificate/CERTIFICATE/p' input
This is a CERTIFICATE Request file:
CERTIFICATE Subject: #有-n无p,不打印任何内容
$ sed -n 's/Certificate/CERTIFICATE/' input #sed替换选项g的用法
#不带g选项,只替换一行的第一个
$ sed -n 's/seu/njue/p' input
It should be mailed to zawu@njue.edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-njuegrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus #带g选项,全部替换
$ sed -n 's/seu/njue/pg' input
It should be mailed to zawu@njue.edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-njuegrid1.njue.edu.cn/OU=njue.edu.cn/CN=globus #sed替换选项w的用法
#将输出结果写入到output文件
$ sed -n 's/seu/njue/w output' input
$ cat output
It should be mailed to zawu@njue.edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-njuegrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus

在运用sed替换文本时,经常用到&符号,&符号等同于被替换的字符串。例如,下面两条命令是等价的:

$ sed -n 's/seu/(&)/pg' input
It should be mailed to zawu@(seu).edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-(seu)grid1.(seu).edu.cn/OU=(seu).edu.cn/CN=globus
$ sed -n 's/seu/(seu)/pg' input
It should be mailed to zawu@(seu).edu.cn
/O=Grid/OU=GlobusTest/OU=simpleCA-(seu)grid1.(seu).edu.cn/OU=(seu).edu.cn/CN=globus

6.写入一个文件

sed命令默认只是对缓冲区中输入文件的副本进行编辑,如果要保存编辑结果,需要将输出结果重定向到另一个文件,举例如下:

#利用w选项进行重定向
$ sed -n '1,5 w output' input
$ cat output
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================ #当然也可以直接将结果重定向,不用w选项
$ sed -n '1,5p' input > output2
$ cat output2
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================

7.从文件中读入文本

运用r选项可从其他文件读文本,追加到指定地址之后,格式为指定地址 r 文件名。下面举例说明选项r的用法,新建一个otherfile文件,内容如下:

This is the first line of the otherfile.
This is the second line of the otherfile.
$ cat otherfile
This is the first line of the otherfile.
This is the second line of the otherfile.
$ sed '/Certificate/r otherfile' input
This is a Certificate Request file:
This is the first line of the otherfile.
This is the second line of the otherfile. It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject:
This is the first line of the otherfile.
This is the second line of the otherfile. /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

8.退出命令

sed命令的q选项表示完成指定地址的匹配后立即退出,不再继续匹配,格式为指定地址 q

例如,打印前5行可以用下面的命令

$ sed '5q' input                #匹配到第5行后推出
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================

例如,要查找任意字符后跟r字符,再跟0个或多个任意字符的字符串,只需用命令sed -n '/.r.*/p' input,若加上q选项,则表示匹配到第一个满足要求的字符串后就退出。如下所示:

$ sed -n '/.r.*/p' input
This is a Certificate Request file:
Certificate Subject:
/O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus
The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file.
/home/alloy/linuxshell/CH02/usercert.pem
$ '/.r.*/q' input
This is a Certificate Request file:

9.变换命令

sed的y选项表示字符变换,它将一系列的字符变为相应的字符,格式如下:

sed 'y/被变换的字符序列/等长的变换字符序列/' 输入文件

例如,命令sed 'y/ABCDE/12345/' input 将input文件中的A变为1、B变为2、C变为3、D变为4、E变为5。

$ sed 'y/ABCDE/12345/' input
This is a 3ertificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================
3ertificate Subject: /O=Grid/OU=GlobusTest/OU=simple31-seugrid1.seu.edu.cn/OU=seu.edu.cn/3N=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/3H02/usercert.pem

需要注意的是,以上均未对原文件进行修改,sed处理的只是缓冲区中原文件的副本。如果要将修改直接保存到原文件,需要带上-i选项。如下所示:

$ cp input ttt                      #创建一个input文件的副本
$ cat ttt
This is a Certificate Request file: It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem
$ sed -i '/file:/a\We append a new line.' ttt #带上-i选项
$ cat ttt
This is a Certificate Request file:
We append a new line. #文件ttt已被修改 It should be mailed to zawu@seu.edu.cn ============================================================
Certificate Subject: /O=Grid/OU=GlobusTest/OU=simpleCA-seugrid1.seu.edu.cn/OU=seu.edu.cn/CN=globus The above string is known as your user certificate subject, and it uniquely identifies this user. $88
To install this user certificate, please save this e-mail message into the following file. /home/alloy/linuxshell/CH02/usercert.pem

awk编程模型

awk程序由一个主输入循环维持,主输入循环反复执行,直到终止条件被触发。主输入循环无需由程序员编写,awk已经搭建好主输入循环的框架,我们编写的代码被嵌入到主输入循环框架中执行,主输入循环自动依次读取输入文件行,而处理文件行的动作是由我们添加的。

awk定义了两个特殊的字段:BEGIN和END。BEGIN用于在主输入循环之前执行,即在未读取输入文件行之前执行,END则相反,用于在主输入循环之后执行,即在读取输入文件完毕之后执行。awk编程模型可以简单地分为三个阶段:

  • 读取输入文件之前执行的代码段(由BEGIN关键字标识)
  • 读取输入文件时执行的代码段
  • 读取输入文件完毕后执行的代码段(由END关键字标识)

调用awk与调用sed类似,也有三种方式:

  • 在Shell命令行输入命令调用awk:awk [-F 域分隔符] 'awk程序段' 输入文件
  • 编辑脚本,然后调用:awk -f awk脚本 输入文件
  • 编辑脚本,设置脚本可执行,然后执行该脚本:./awk脚本 输入文件

awk编程实例

1.awk模式匹配

任何awk语句都由模式(pattern)和动作(action)组成。模式是一组用于测试输入行是否需要执行动作的规则,动作是包含语句、函数和表达式的执行过程。在模式匹配中,awk支持扩展的正则表达式。

$ awk '/^$/{print "This is a blank line."}' input
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.

上例是awk的第一种调用方式,单引号中间是awk命令。//中间是模式,{}中间是动作。该awk命令表示一旦读入的输入文件行是空行,就打印“This is a blank line.”下面是第二种和第三种调用方式:

$ cat scr.awk
/^$/{print "This is a blank line."}
$ awk -f scr.awk #第二种方式
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line. $ cat scr.awk
#!/usr/bin/awk -f /^$/{print "This is a blank line."}
$ chmod u+x scr.awk
$ ./scr.awk #第三种方式
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.

2.记录和域

awk认为输入文件是结构化的,awk将每个输入文件行定义为记录,行中的每个字符串定义为域,域之间用空格、Tab键或其他符号进行分隔,分隔域的符号就叫分隔符。

awk定义域操作符$来指定执行动作的域,域分隔符$后面跟数字或变量来标识域的位置。每条记录的域从1开始编号,如$1表示第1个域,$2表示第2个域,$0表示所有的域。

$ cat record
Li Hao njue 025-83481010
Zhang Ju nju 025-83466534
Wang Bin seu 025-83494883
Zhu Lin njupt 025-83680010
#按照2、1、4、3的顺序打印record的域
$ awk '{print $2,$1,$4,$3}' record
Hao Li 025-83481010 njue
Ju Zhang 025-83466534 nju
Bin Wang 025-83494883 seu
Lin Zhu 025-83680010 njupt
#打印record的所有域
$ awk '{print $0}' record
Li Hao njue 025-83481010
Zhang Ju nju 025-83466534
Wang Bin seu 025-83494883
Zhu Lin njupt 025-83680010

域操作符$后面还可以跟变量,或者变量运算表达式,如下所示:

$ awk 'BEGIN {one=1;two=2} {print $(one+two)}' record
njue
nju
seu
njupt

BEGIN字段中定义了one和two两个变量并赋值,print语句后跟$(one+two)变量运算表达式,one+two=3,因此该命令打印record的第3个域。

上面两个例子的分隔符都是空格符,这是awk的默认设置,Tab键被看作连续的空格键来处理,我们可以使用awk的-F选项来改变分隔符。下面以Tab键为分隔符,展示-F用法:

#以Tab键为分隔符打印第3个域
$ awk -F"\t" '{print $3}' record
025-83481010
025-83466534
025-83494883
025-83680010

我们也可以在BEGIN字段中通过设置awk环境变量FS的值来改变分隔符。首先把record文件内容改为用逗号分隔,如下所示:

$ cat record
Li Hao,njue,025-83481010
Zhang Ju,nju,025-83466534
Wang Bin,seu,025-83494883
Zhu Lin,njupt,025-83680010
#以逗号为分隔符打印第1、3个域
$ awk 'BEGIN {FS=","} {print $1,$3}' record
Li Hao 025-83481010
Zhang Ju 025-83466534
Wang Bin 025-83494883
Zhu Lin 025-83680010

3.关系和布尔运算符

awk定义了一组关系运算符用于awk模式匹配,关系运算符及其意义如下:

运算符 意义
< 小于
> 大于
<= 小于或等于
>= 大于或等于
== 等于
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式

/etc/passwd文件记录了Linux系统用户的关键信息,系统的每一个合法用户账号对应于该文件中的一行记录。这行记录定义了每个用户账号的属性。下面是一个passwd文件的部分内容。

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

在该文件中,每一行用户记录的各个域用:分割,分别定义了用户的各方面属性。各字段的顺序和含义为:注册名:密码:用户标识号:组标识号:用户名:用户主目录:命令解释程序

#第1个域匹配root
$ awk 'BEGIN {FS=":"} $1~/root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
#所有域不匹配nologin
$ awk 'BEGIN {FS=":"} $0!~/nologin/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
...

awk在进行模式匹配时,常常使用条件语句,awk条件语句与C语言类似。下面用if语句匹配第3个域小于第4个域的记录:

$ awk 'BEGIN {FS=":"} {if($3<$4) print $0}' /etc/passwd
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
...

awk还定义了与、或、非三个逻辑运算符,&& || !用法同C语言,下面举例说明:

#第3个域等于10或第4个域等于10(==精确匹配)
$ awk 'BEGIN {FS=":"} {if($3==10 || $4==10) print $0}' /etc/passwd
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
#第3个域匹配10或第4个域匹配10(~以正则表达式匹配)
$ awk 'BEGIN {FS=":"} {if($3~10 || $4~10) print $0}' /etc/passwd
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
messagebus:x:106:110::/var/run/dbus:/bin/false
uuidd:x:107:111::/run/uuidd:/bin/false
lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false
whoopsie:x:109:116::/nonexistent:/bin/false
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false

4.表达式

与其他语言类似,awk表达式用于存储、操作和获取数据,一个awk表达式可由数值、字符常量、变量、操作符、函数和正则表达式等组合而成。

awk变量名只能包含字母、数字、下划线,而且不能以数字开头,区分大小写。定义awk变量无须声明变量类型,每个变量有两种类型的值:字符串值和数值。awk根据表达式上下文来确定使用哪个值,变量的默认数值为0,默认字符串值为空。

x=1                 #x赋值为1
y="Good" #y赋值为"Good"
z="Very" "Good" #z赋值为"VeryGood",等同于z="VeryGood" $ awk 'BEGIN{FS=":"; x=1; y="Good"; z="Very" "Good"} {if($3==10 || $4==10) {print x; print y; print z}}' /etc/passwd
1
Good
VeryGood

表达式可进行变量和数字之间的算术操作,awk算术运算符如下:

运算符 意义
+
-
*
/
%
^ 乘方
++ 自增(包含前置++和后置++,区别同C语言)
匹配空行并记录次序打印
$ awk '/^$/{print x+=1}' input #x默认值为0
1
2
3
4
5

再举一个例子,我们在record文件中每个学生记录后加入5个域,表示学生5门学科的考试成绩,record文件内容如下:

Li Hao,njue,025-83481010,85,92,78,94,88
Zhang Ju,nju,025-83466534,89,90,75,90,86
Wang Bin,seu,025-83494883,84,88,80,92,84
Zhu Lin,njupt,025-83680010,98,78,81,87,76

接着编写一个名为scr2.awk的脚本计算record 文件中每个学生的平均成绩并打印出来。

#!/usr/bin/awk -f

BEGIN {FS=","}
{total=$4+$5+$6+$7+$8
avg=total/5
print $1,avg}

执行脚本后结果如下:

$ chmod u+x scr2.awk
$ ./scr2.awk record
Li Hao 87.4
Zhang Ju 86
Wang Bin 85.6
Zhu Lin 84

5.系统变量

awk定义了很多内置变量用于设置环境信息,我们称之为系统变量。这些系统变量的作用可以分为两种。第一种用于改变awk的默认值,如域分隔符;第二种用于定义系统值,在处理文件时可以读取这些系统值,如记录中的域数量、当前记录数、当前文件名等。常见awk环境变量及其意义如下:

变量名 意义
$n 当前记录的第n个域,域间由FS分隔
$0 记录的所有域
ARGC 命令行参数的数量
ARGV 命令行参数的数组
ARGIND 命令行中当前文件的位置(以0开始编号)
CONVFMT 数字转换格式
ENVIRON 环境变量关联数组
ERRNO 最后一个系统错误的描述
FIELDWIDTHS 字段宽度列表,以空格键分隔
FILENAME 当前文件名
FNR 浏览文件的记录数
FS 域分隔符,默认是空格键
RS 记录分隔符,默认是空格键
NF 当前记录中的域数量
NR 当前记录数
OFS 输出域分隔符,默认是空格键
ORS 输出记录分隔符,默认是换行符
OFMT 数字的输出格式
RLENGTH 由match函数所匹配的字符串长度
RSTART 由match函数所匹配的字符串的第1个位置
SUBSEP 数组下标分隔符,默认值是\034

6.格式化输出

awk借鉴了C语言的语法,定义了printf输出语句,它可以规定输出的格式。awk中printf的用法与C语言中printf一致,熟悉C语言的话,这部分内容就很简单。

printf修饰符及其意义:

修饰符 意义
- 左对齐
width 域的宽度
.prec 小数点右边的位数

printf格式符及其意义:

格式符 意义
%c ASCII字符
%d 整型数
%e 浮点数,科学计数法
%f 浮点数
%s 字符串
%o 八进制数
%x 十六进制数
#参数是变量列表
$ awk 'BEGIN {FS=","} {printf("%s\t%d\n", $2, $8)}' record
njue 88
nju 86
seu 84
njupt 76 #转化为ASCII字符
$ awk 'BEGIN {printf("%c\n", 65)}'
65 #转换为浮点数
$ awk 'BEGIN {printf("%f\n", 2010)}'
2010.000000 #修饰符-和width的用法
$ awk 'BEGIN {FS=","} {printf("%-15s\t%s\n", $1, $3)}' record
Li Hao 025-83481010
Zhang Ju 025-83466534
Wang Bin 025-83494883
Zhu Lin 025-83680010 #想在输出的域上加上解释语言,可在BEGIN字段中加上即可
$ awk 'BEGIN {FS=","; print "Name\t\tPhonenumber"} {printf("%-15s\t%s\n", $1, $3)}' record
Name Phonenumber
Li Hao 025-83481010
Zhang Ju 025-83466534
Wang Bin 025-83494883
Zhu Lin 025-83680010

7.内置字符串函数

awk提供了内置字符串函数,用于实现文本的字符串替换、查找以及分割等功能。awk字符串函数及其意义如下:

函数名 意义
gsub(r,s) 在输入文件中用s替换r
gsub(r,s,t) 在t中用s替换r
index(s,t) 返回s中第一个t出现的位置
length(s) 返回s的长度
match(s,t) 测试s是否包含t,t可以是一个正则表达式(若匹配成功,返回匹配t的首位置;若不成功,则返回0)
split(r,s,t) 以t为分隔符,将r拆分为字符串数组,将结果存到数组s中,并返回分割结果的数量
split(r,s) 以FS为分隔符,将r拆分为字符串数组,将结果存到数组s中,并返回分割结果的数量
sub(r,s) 将记录中的第一次出现的r替换为s
sub(r,s,t) 将t中第一次出现的r替换为s
substr(r,s) 返回字符串r中从s开始的后缀部分
substr(r,s,t) 返回字符串r中从s开始长度为t的部分
toupper(s) 返回字母全为大写的s
tolower(s) 返回字母全为小写的s
#gsub函数用法
#替换第1个域上的root字符串
$ awk 'BEGIN {FS=":"; OFS=":"} gsub(/root/, "gridsphere", $1)' /etc/passwd
gridsphere:x:0:0:root:/root:/bin/bash
#替换全部域上的root字符串
$ awk 'BEGIN {FS=":"; OFS=":"} gsub(/root/, "gridsphere", $0)' /etc/passwd
gridsphere:x:0:0:gridsphere:/gridsphere:/bin/bash
$ awk 'BEGIN {FS=":"; OFS=":"} gsub(/root/, "gridsphere")' /etc/passwd
gridsphere:x:0:0:root:/root:/bin/bash #index函数用法
$ awk 'BEGIN {print index("gridsphere", "ph")}'
6 #length函数用法
$ awk 'BEGIN {print length("gridsphere")}'
10 #match函数用法
$ awk 'BEGIN {print match("gridsphere", /ph/)}'
6
$ awk 'BEGIN {print match("gridsphere", /Ph/)}'
0 #sub函数用法
$ awk 'BEGIN {str="multiprocessor programming"; sub(/pro/, "PRO", str); printf("%s\n", str)}'
multiPROcessor programming
$ awk 'BEGIN {FS=","} {$1~/Li Hao/ sub(/10/, "onezero", $0); print $0}' record
Li Hao,njue,025-8348onezero10,85,92,78,94,88 #记录只有第1个10被替换
Zhang Ju,nju,025-83466534,89,90,75,90,86
Wang Bin,seu,025-83494883,84,88,80,92,84
Zhu Lin,njupt,025-836800onezero,98,78,81,87,76 #substr函数用法
$ awk 'BEGIN {str="multiprocessor programming"; print substr(str, 6)}'
processor programming
$ awk 'BEGIN {str="multiprocessor programming"; print substr(str, 6, 9)}'
processor

split函数用法放在后面数组那一节介绍。

8.向awk脚本传递参数

awk脚本内的变量可以在命令行中赋值,实现向awk脚本传递参数,格式如下:

awk脚本 parameter=value 输入文件

awk所传递的参数可以是自定义的变量,也可以是系统变量。下面举例说明,首先新建一个pass.awk脚本如下:

#!/usr/bin/awk -f

{if(NF!=MAX)
print("The line "NR" does not have "MAX" fields")}

pass.awk脚本利用一个判断条件NF!=MAX,表示记录的域数量是否等于MAX变量,若不等于,则输出包含NR和MAX两个变量的文本行。下面给出pass.awk脚本的执行结果,并在输入文件之前加上两条赋值语句,分别对MAX和FS变量赋值,语句之间用空格隔开,需要注意的是,=两端不能有空格。

$ chmod u+x pass.awk
$ ./pass.awk MAX=3 FS="," record
The line 1 does not have 3 fields
The line 2 does not have 3 fields
The line 3 does not have 3 fields
The line 4 does not have 3 fields

再举一个例子,输出record的所有记录,每条记录前,加上了其行号(即NR变量),然后重新设置OFS为.,改变输出域的分隔符。

$ awk 'BEGIN {FS=","} {print NR,$0}' OFS="." record
1.Li Hao,njue,025-83481010,85,92,78,94,88
2.Zhang Ju,nju,025-83466534,89,90,75,90,86
3.Wang Bin,seu,025-83494883,84,88,80,92,84
4.Zhu Lin,njupt,025-83680010,98,78,81,87,76

需要注意的是,命令行参数不能被BEGIN字段语句访问。换句话说,直到输入文件的第1行被读取时,命令行参数才生效。

$ awk '
> BEGIN {print n}
> {if(n==1) print "Reading the "NR" line."
> }' n=1 record Reading the line 1.
Reading the line 2.
Reading the line 3.
Reading the line 4.

上例在BEGIN字段打印变量n的值,因为命令行参数不能被BEGIN字段访问,即n相当于未赋值,默认为空,所以打印出了空行;在主循环中判断n的值,通过命令行赋值n=1,满足n==1条件,打印出相应的语句。

9.条件语句和循环语句

awk条件语句和循环语句的语法与C语言一致。不做过多介绍,只列出基本形式:

#if语法
if(布尔语句)
动作1
[else #else不是必须的
动作2] #while语法
while(布尔语句)
动作 #do while语法
do
动作
while(布尔语句) #for语法
for(初始设置;判断条件;计数器变化)
动作

10.数组

awk数组的形式与C语言大体一致。不过,awk数组无须定义数组类型和大小,可以直接赋值后使用。

1.关联数组

关联数组是指数组的索引可以是字符串,也可以是数字。在一些编程语言中,数组的索引只能是数字,数组表示了存储值的一些列地址,数组索引是由存储地址的顺序决定的,如C语言中array[0]表示数组array的第1个元素。而关联数组在索引和数组元素值之间建立起关联,对数组每一个元素,awk自动维护了一对值:索引和数组元素值。关联数组的值无须以连续的地址进行存储,因此,关联数组即便以数字作为索引,但是该索引并不表示数组存储地址的信息。awk的所有数组都是关联数组。

基于关联数组,awk特别定义了一种for循环来访问关联数组,语法如下:

for(index in array)
do something with array[index]

array是已定义的数组名,index是任意指定的变量。注意,此处index相当于索引,而不是元素值。

$ awk 'BEGIN{a[0]=1; a[2]=3; a["ss"]=4; a["tt"]="fail"; for(i in a) print i, a[i]}'
ss 4
tt fail
0 1
2 3

关键字in也可用于判断元素是否在数组中,格式为index in array

$ awk 'BEGIN{data[10.15]="1200"; if(10.15 in data) print "Found element."}'
Found element.

2.split函数

split函数以t为分隔符,将r拆分为字符串数组,并将结果存到数组s中。

#将abc/def/xyz分割为3个元素
$ awk 'BEGIN{print split("abc/def/xyz", str, "/"); for(i in str) {print str[i]}}'
3
abc
def
xyz
#将record文件的第1域分割为2个元素
$ awk 'BEGIN{FS=","} {print split($1, name, " ")}' record
2
2
2
2

3.数组形式的系统变量

awk系统变量中有两个变量是以数组形式存在的:ARGV和ENVIRON。ARGC是ARGV数组中元素的个数,与C语言相同,从ARGV[0]开始,到ARGV[ARGC-1]结束。下面通过一个例子查看ARGV中到底存放了哪些命令行参数,新建一个脚本argv.awk,内容如下:

#!/usr/bin/awk -f

BEGIN{
print ARGC
for(x=0; x<ARGC; ++x)
print ARGV[x]
}

接下来在执行该脚本,结果如下:

$ chmod u+x argv.awk
$ ./argv.awk #不传递参数
1
awk
$ ./argv.awk xyz n=99 "Hello World" #传递三个参数
4
awk
xyz
n=99
Hello World

说明ARGC是实际传递的参数个数加1,且数组ARGV的第一个元素ARGV[0]为awk

接下来,举一个相对复杂的例子来说明ARGV的应用,对record文件中的电话号码进行检索,即输入学生姓名,系统相应输出其电话号码。新建名为findphone.awk的脚本如下:

#!/usr/bin/awk -f

BEGIN{
FS=",";
if(ARGC>2){
name=ARGV[1];
delete ARGV[1];
}
else{
while(!name){
print "Pls. Enter a name";
getline name< "-"
}
}
}
$1~name {print $1,$3}

findphone.awk脚本表示,若ARGC大于2,说明用户已经输入需要查找的姓名,则将ARGV[1]赋给name变量;若ARGC不大于2,说明此时未输入姓名,则利用循环提示输入姓名,利用getline函数将输入赋给name变量(关于getline可查看这两篇文章:awk getline命令解析awk输入命令getline)。主输入循环变量判断第1个域是否与name匹配,若匹配则输出第1个域和第3个域的值。演示结果如下:

$ chmod u+x findphone.awk
$ ./findphone.awk Zhu record
Zhu Lin 025-83680010
$ ./findphone.awk record
Pls. Enter a name
$ Li
Li Hao 025-83481010
Zhu Lin 025-83680010

ENVIRON变量存储了Linux操作系统的环境变量,下面这个例子,打印出ENVIRON数组的所有内容,其结果类似于Linux的set命令,它显示了当前系统定义的所有环境变量。

$ awk '
> BEGIN{for(i in ENVIRON)
> print i "=" ENVIRON[i]}'
XDG_SESSION_DESKTOP=ubuntu
QT_IM_MODULE=fcitx
DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path
SESSION=ubuntu
GNOME_KEYRING_CONTROL=
DESKTOP_SESSION=ubuntu
...

小结

本章介绍了sed和awk,sed用于流编辑,它将一系列的编辑命令作用于缓冲区中输入文件的副本,从而实现对输入文件的各种操作。而awk的一大显著特点是处理结构化文件。所谓结构化文件,是指划分为记录和域的文件,对此,awk提供了printf语句能生成格式化报表。

上一篇:Date类型之继承方法


下一篇:Rsync同步部署web服务端配置