8.2 grep和正则表达式
使用正则表达式使模式匹配加入一些规则,因此可以在抽取信息中加入更多选择。使用
正则表达式时最好用单引号括起来,这样可以防止grep中使用的专有模式与一些shell命令的
特殊方式相混淆。
8.2.1 模式范围
示例:(抽取包含483或484的行)
$ grep '48[34]' data.f
8.2.2 不匹配行首
如果要抽出记录,使其行首不是48,可以在方括号中使用^记号,表明查询在行首开始。
$ grep '^[^48]' data.f
8.2.3 设置大小写
使用- i开关可以屏蔽月份Sept的大小写敏感,也可以用[ ]模式。
8.2.4 匹配任意字符
匹配头两个是大写字母,并以C结尾的行。
$ grep '[A-Z] [A-Z]C$' test_file
8.2.5 模式出现几率
抽取包含数字4至少重复出现两次的所有行,方法如下:
$ grep '4\{2,\}' data.f
483 May 5PA1998 USP 37.00 KVM9D 644
如果要查询重复出现次数一定的所有行,语法如下,数字9重复出现两次:
$ grep '9\{2\}' data.f
有时要查询重复出现次数在一定范围内,比如数字或字母重复出现2到6次,下例匹配数
字8重复出现2到6次,并以3结尾:
$ grep '8\{2,6}3$' myfile
83 -- not match
88883 -- match
8884 -- not match
8888883 -- match
8.2.6 使用grep匹配“与”,“或”模式
grep 命令加-E 参数,这一扩展允许使用扩展模式匹配。例如,要抽取城市代码为219或
216,方法如下:
$ grep -E '219|216' data.f
219 Dec 2CC1999 CAD 23.00 PLV2C 68
216 Sept 3ZL1998 USP 86.00 KVM9E 234
8.2.7 匹配空行
结合使用^和$可查询空行。使用- n参数显示实际行数:
$ grep '^$' myfile
8.2.8 匹配特殊字符
查询有特殊含义的字符,诸如$ . ' " * [] ^ | \ + ? ,必须在特定字符前加\。假设要查询包含“.”
的所有行,脚本如下:
如要查询文件名conftroll.conf(这是一个配置文件),脚本如下:
$ grep 'conftroll\.conf' myfile
8.3 类名
grep允许使用国际字符模式匹配或匹配模式的类名形式。
表8-1 类名及其等价的正则表达式
类 等价的正则表达式 类 等价的正则表达式
[[:upper:]] [A-Z] [[:alnum:]] [0-9a-zA-Z]
[[:lower:]] [a-z] [[:space:]] 空格或tab键
[[:digit:]] [0-9] [[:alpha:]] [a-zA-Z]
现举例说明其使用方式。要抽取产品代码,该代码以5开头,后跟至少两个大写字母。使
用的脚本如下:
$ grep '5[[:uppper:]][[:upper:]]' data.f
483 Sept 5AP1996 USP 65.00 LVX2C 189
483 May 5PA1998 USP 37.00 KVM9D 644
之前说过,[字符]* 这个组合匹配的结果是包含此字符0个或多个的集合。
举例:testfile文件的内容如下:
looks
likes
ls
ls123
那么,我们输入以下命令后的结果分别如下:
$ grep 'l.*s' testfile
looks
likes
ls
ls123
是指包含l,s,且l,s之间可有0个,1个,或多个字符。
$ grep 'l.*k.' testfile
looks
likes
是指包含l,k,且l,k之间可有0个,1个,或多个字符。k之后至少要有一个字符。
$ grep 'ooo*' testfile
looks
*与其之前的o一起,那么搜索的匹配是包含2个或更多个连续的字符o的字符串。
如果想屏蔽终端上的错误提示,可以将标准输出和标准错误都重定向到/dev/null。
如: grep "louise" /etc/password > /dev/null 2>&1
8.4 egrep
egrep代表expression 或extended grep,适情况而定。egrep接受所有的正则表达式,egrep
的一个显著特性是可以以一个文件作为保存的字符串,然后将之传给egrep作为参数,为此使
用-f开关。如果创建一个名为grepstrings的文件,并输入484和47:
$ cat grepstrings
484
47
$ egrep -f grepstrings data.f
上述脚本匹配data.f中包含484或47的所有记录。当匹配大量模式时, - f开关很有用,而在
一个命令行中敲入这些模式显然极为繁琐。
第9章 Awk
如果要格式化报文或从一个大的文本文件中抽取数据包,那么awk可以完成这些任务。它
在文本浏览和数据的熟练使用上性能优异。
为获得所需信息,文本必须格式化,即用域分隔符划分抽取域。
9.1 调用awk
awk [-F field-seperator] 'commands' input-file(s)
commands是真正的awk命令
如果设置了- F选项,则awk每次读一条记录或一行,并使用指定的分隔符分隔指定域,但
如果未设置- F选项,awk假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现
时,awk命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件
尾或文件不再存在。
执行awk脚本:awk -f awk-script-file input-files
其中,-f选项指明在文件awk-script-file中的awk脚本。input-file(s)是要使用脚本处理的文件。
使用$1 , $3表示参照第1和第3域,$0表示所有域。举例如下:
为加入tab键,使用tab键速记引用符\t,后面将对速记引用加以详细讨论。也可
以为输出文本加入信息头。本例中加入name和belt及下划线。下划线使用\n,强迫启动新行,
并在\n下一行启动打印文本操作。打印信息头放置在BEGIN模式部分,因为打印信息头被界
定为一个动作,必须用大括号括起来。
$ awk 'BEGIN { print "Name Belt\n-------------------------------------"}
{ print $1"\t"$4} END {"----------------------------------\nend-of-report\n"}' grade.txt
Name Belt
------------------------------
M.ZTansley Green
J.Lulu Yellow
------------------------------
end-of-report
当第一次使用awk时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总
结出以下规则。在碰到a w k错误时,可相应查找:
? 确保整个awk命令用单引号括起来。
? 确保命令内所有引号成对出现。
? 确保用花括号括起动作语句,用圆括号括起条件语句。
? 可能忘记使用花括号,也许你认为没有必要,但awk不这样认为,将按之解释语法。
9.2 元字符
\ ^ $ . [] | () * + ?
其中, + 和 ? 只适用于awk。
+ 使用+ 匹配一个或多个字符。
?匹配模式出现频率。例如使用/XY?Z/匹配XYZ或YZ。
9.2.1 条件操作符
操 作符 描述 操作符 描述
< 小于 > = 大于等于
<= 小于等于 ~ 匹配正则表达式
== 等于 !~ 不匹配正则表达式
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式
(1)匹配
为使一域号匹配正则表达式,使用符号'~’后紧跟正则表达式,也可以用i f语句。a w k
中if后面的条件用()括起来。
观察文件grade.txt,如果只要打印brown腰带级别可知其所在域为field-4,这样可以写出
表达式{if($4~/brown/) print }意即如果field-4
包含
brown,打印它。如果条件满足,则打印匹
配记录行。可以编写下面脚本,因为这是一个动作,必须用花括号{}括起来。
$ awk '{ if($4~/Brown/) print $0}' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
匹配记录找到时,如果不特别声明, awk缺省打印整条记录。
(2)精确匹配
~得到的结果是包含指定匹配串的行。但如果要精确匹配,需要使用== 。示例如下:
$ awk '$3=="48" {print $0}' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
(3)不匹配
在~之前加入!即可。下面的例子是找出
field-4
不
包含
brown的行。
$ awk '{ if($4 !~/Brown/) print $0}' grade.txt
其他比较操作(大于,大于等于 等操作符的使用不再赘述。
(4)设置大小写
为查询大小写信息,可使用[]符号。在测试正则表达式时提到可匹配[]内任意字符或单词,
因此若查询文件中级别为green的所有记录,不论其大小写,表达式应为'/[Gg]reen/'
$ awk '/[Gg]reen/' grade.txt
M.ZTansley Green
(5)匹配任意字符
使用句点.可匹配任意字符。表达式/^...a/意为行首前三个字符任意,第四个是a,尖角符号代表行首。
$ awk '$1 ~/^...a/' grade.txt
如果查询文本文件行首包含4 8的代码,可简单使用下面^符号:
$ awk '/^48/' input-file
(6)或关系匹配
为抽取级别为yellow或brown的记录,
使用竖线符|。意为匹配| 两边模式之一
。注意,使
用竖线符时,语句必须用圆括号括起来。
$ awk '$0 ~/(Green|green)/' grade.txt
(7)复合操作符
复合模式或复合操作符用于形成复杂的逻辑操作(逻辑判断)。注意跟上面的“或关系匹配”区分开来。
&& AND :语句两边必须同时匹配为真。
|| OR :语句两边同时或其中一边匹配为真。
! 非 : 求逆
(8)设置输入域到域变量名
在awk中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。
一般的变量名设置方式为name = $ n,这里name为调用的域变量名, n为实际域号。例如设置学
生域名为name,级别域名为belt,操作为name=$1;belts = $4。注意分号的使用,它分隔awk命
令。下面例子中,重新赋值学生名域为name,级别域为belts。查询级别为Yellow的记录,并
最终打印名称和级别。
$ awk '{name = $1; belts = $4; if(belts ~/Yellow/) print name "is blet"
blets }' grade.txt
P.Bunny is belt Yellow
(9)赋值比较操作
下面的例子查询所有比赛中得分在27点以下的学生。
用引号将数字引用起来是可选的,“27”、27产生同样的结果。
$ awk '{if ($6 < 27) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
下例中给数字赋以变量名BASELINE和在BEGIN部分给变量赋值,两者意义相同。
$ awk 'BEGIN {BASELINE="27"} if($6 < BASELINE) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
(10)修改数值域取值或文本域
当在awk中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是
保存在缓存里的awk副本。
也就是说我们看到的awk的结果是缓存里面的副本,是被修改过的。但是如果我们使用cat
命令查看文件,会发出先文件还是未被修改。
$ awk '{ if($1 == "Build") $1 = "test"} { print $0} test_file
$ cat test_file
下面的例子实现计算当前目录下所有文件的长度(KB)。
首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是
一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应
适时加以改变。
$ ls -l | awk ' /^[^d]/ {print $9"\t"$5} {tot += $5} END {print "total :" tot "KB" }'
dev_pkg.fail 345
failedlogin 12416
messages 4260
sulog 12810
utmp 1856
wtmp 7104
total : 41351 KB
9.2.2 awk内置变量
awk 有许多内置变量用来设置环境信息。这些变量可以被改变。
ARGC (Argument Count) 命令行参数个数
ARGV (Argument Values) 命令行参数排列
ENVIRON (Environment) 支持队列中系统环境变量的使用
FILENAME (Filename) awk浏览的文件名
FNR (Number of Records in File) 浏览文件的记录数
FS (File Separator) 设置输入域分隔符,等价于命令行 - F选项
NF (Number of Field) 浏览记录的域个数
NR (Number of Records) 已读的记录数
OFS (Output Field Separator) 输出域分隔符
ORS (Output Record Separator) 输出记录分隔符
RS (Record Separator) 控制记录分隔符
ARGC支持命令行中传入awk脚本的参数个数。ARGV是ARGC的参数排列数组,其中每
一元素表示为ARGV[n],n为期望访问的命令行参数。
ENVIRON 支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如
ENVIRON[“EDITOR”] =“Vi”。
FILENAME支持awk脚本实际操作的输入文件。因为awk可以同时处理许多文件,因此如
果访问了这个变量,将告之系统目前正在浏览的实际文件。
FNR为awk目前操作的文件中的记录数。其变量值小于等于NR。如果脚本正在访问许多文件,
每一新输入文件都将重新设置此变量。
FS用来在awk中设置域分隔符,与命令行中-F选项功能相同。缺省情况下为空格。如果用
逗号来作域分隔符,设置FS = ","。
NR为已读的记录数。所以在浏览开始,其值为0,浏览结束后,其值为记录个数。
NF支持记录域个数,在记录被读之后再设置。
OFS允许指定输出域分隔符,缺省为空格。如果想设置为#,写入OFS=" # "。
ORS为输出记录分隔符,缺省为新行(\n)。
RS是记录分隔符,缺省为新行(\n)。
示例:
$ awk 'BEGIN{print NR,FNR} END{print NR,FNR}' test_file1 test_file2
$ awk -F : '{OFS="\t"} {print $1,$7}' /etc/passwd
9.2.3 awk内置的字符串函数
gsub(r,s) 在整个$0中用s替代r
gsub(r,s,t) 在整个t中用s替代r
index(s,t) 返回s中字符串t的第一位置
length(s) 返回s长度
match(s,r) 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint(fmt,exp) 返回经fmt格式化后的exp
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字
符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,
然而,如果未指定目标,缺省为$0。
index(s,t)函数返回目标字符串s中查询字符串t的首位置。
length函数返回字符串s字符
长度。
match函数测试字符串s是否包含一个正则表达式r定义的匹配。
split使用域分隔符fs将
字符串s划分为指定序列a。
sprint函数类似于printf函数(以后涉及),返回基本输出格式fmt的结果字符串exp。
sub(r,s)函数将用s替代$0中最左边最长的子串,该子串被(r)匹配。
sub(s,p)返回字符串s在位置p后的后缀。substr(s,p,n)同上,并指定子串长度为n。
(1)gsub
要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式。
示例如下:
$ awk 'gsub(/build/,"test") {print $0}' test_file
autotest
test
test_linux_prog
test_linux_ko
gsub也是修改的缓存中的副本,原文件内容并未被修改。所以原文件对应的几行还是:
autobuild
build
build_linux_prog
build_linux_ko
(2)index
查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串
Bunny中ny出现的第一位置,即字符个数。
$ awk 'BEGIN { print index("Bunny","ny")}' grade.txt
4
(3)length
返回所需字符串长度。示例如下:
$ awk 'BEGIN { print length("Bunny")}'
5
(4)match
match测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返
回值为第一次出现的字符位置。否则,返回0。
$ awk 'BEGIN { print match("ABCD",/d/)}'
0
$ awk 'BEGIN { print match("ABCD",/C/)}'
3
$ awk 'BEGIN { print match("ABCD","C")}'
3
(5)split
与在awk命令中设置FS或指定-F选项。返回拆分后的数组元素个数。
$ awk 'BEGIN { print split("ab-cd-ef-g",Myarray,"-")}
{ print Myarray[1],Myarray[2],Myarray[3],Myarray[4]}' test_file
4
ab cd ef g
上例中的test_file输入文件必须指定(从语法上),但实际上不起作用。
(6)sub
使用sub发现并替换模式的第一次出现位置。
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub("-","#",STR)} END{print STR}' test_file
ab#cd#ef#g
或者
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub(/-/,"#",STR)} END{print STR}' test_file
ab#cd#ef#g
但是第二个替换串不能是模式。示例如下:
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub(/-/,/#/,STR)} END{print STR}' test_file
ab0cd0ef0g
(7)substr
substr是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
$ awk '$1=="L.Tansley" {print substr($1,1,5)}' grade.txt
L.Tan
上例中,指定从第一个域第1个字符开始,长度为5的字串。
如果给定长度值远大于字符串长度, awk将从起始位置返回所有字符,要抽取L.Tansley
的姓,只需从第3个字符开始返回长度为7。可以输入长度99,awk返回结果相同。
substr的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及
其返回字串的起始位置。
$ awk '{print substr($1,3)}' grade.txt
(7)从shell中向awk传入字符串
可以通过管道的方式,如:
$ echo "Stand-by" | awk ' { print length($0)}'
8
9.2.4 字符串屏蔽序列
使用字符串或正则表达式时,有时需要在输出中加入一新行或查询一元字符。
打印一新行时,(新行为字符\n),给出其屏蔽序列,以不失其特殊含义,用法为在字符
串前加入反斜线。例如使用\n强迫打印一新行。
如果使用正则表达式,查询花括号({}),在字符前加反斜线,如/\{/,将在awk中失掉其
特殊含义。