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语句能生成格式化报表。