11.2.1grep和正则表达式
让我们首先从grep命令开始。这个命令大家应该很熟悉了,它用来在文件中查找一个
字符串。不过,实际上,grep的处理功能要强大和复杂的多。
grep命令的语法是
grep[模式][文件名]
如果没有给出文件名,就缺省使用标准输入。grep每次读取一行,并且和给出的模式
进行匹配,如果成功就把这一行会显,例如:(粗体的是我们输入的内容)
$greptest
close
testmyhand
testmyhand
grep的“模式”也称为正则表达式,可以由各种基本的正则表达式元素构成。正则表
达式元素主要包括下面几种:
字符串匹配任何字符串,例如greptest表示在标准输入中1
[...]封闭集中匹配一个字符,如:[abcde]可以匹配a,b,c,d,e
[^...]求补集中匹配一个字符,例如[^ABC]匹配
.匹配任意字符
/s空白符
/S非空白符
/d数字
/D非数字
/w字母或数字
/W非字母和数字
*匹配任何字符
上面的形式是grep中使用的基本正则表达式,另外,还可以使用egrep,egrep是grep
的一个扩展版本,支持下面这些扩展的正则字符串:
^匹配一行的开始
$匹配一行的结尾
()确定正则表达式求值顺序,和正常演算中的括号意思差不多。
(...|...|...)或,可选项之一进行匹配,例如:(abc|dev|ghi)可以匹配abc,dev,gh
i,而(ww|gg)do可以匹配wwdo或者ggdo。
+一次或多次模式
如:aba+匹配aba,abaa...不匹配ab
通常,我们有两种方法使用grep和egrep,一种是使用管道,例如我们应该熟悉的ps
ax|grepsendmail,另一种是直接在文件中搜索对应的字符串。
grep/egrep还可以在命令行使用开关,常用的开关包括:
-b在行前加上块号
-c统计匹配行的个数
-n在行前加上行号
-w将模式解释为字符串,所有正则表达式的控制命令失效
-x精确匹配
-r查询文件时包含子目录
举个例子来说,我们想在/var/log/httpd/access_log中查询所有不是来自本地(192
.168.0.1)的请求记录,可以执行:
grep–v"^192.168.0.1"/var/log/httpd/access_log
^用来让grep只在行首匹配。
在grep查询的时候可以使用通配符代表多个文件,例如,grepstart*-r将在当前目
录以及所有子目录的所有文件中查询start字符串。
11.2.2gawk的使用方法
gawk是awk的一个实现,awk是一种用来处理报告等文本文件的脚本语言。不过,我们
介绍这个产品的主要目标是用它来处理各种程序的记账文件。对于复杂的脚本,还是用
Perl比较合适。
gawk的主要功能是针对档案的每一行搜寻指定的模式。,每当找到一个匹配的模式
,gawk就会去执行你设定的动作。按照这个方式,gawk依此方式处理输入档案的每一
行直到输入档案结束。如果对于某个模式没有设置对应的动作,gawk将直接将这个行显
示出来。
为了使用gawk,你通常必须先写一个awk脚本,除非模式/动作非常简单,可以在一行
上完成。我们用一个例子来解释gawk的基本用法,首先产生一个目录列表文件:
ls–l/etc>list
现在list的内容有点像这样:
total2164
drwxr-xr-x3rootroot4096Feb1522:55CORBA
-rw-r--r--1rootroot2045Sep241999DIR_COLORS
-rw-r--r--1rootroot17Mar2519:59HOSTNAME
…………
现在我们选择一个最简单的例子,简单地查找所有属性是drwxr-xr-x的目录文件:
gawk'/drwxr-xr-x/{print$0}'list
将输出所有这样的目录。
这个例子看上去没有什么实际用处,因为用grep也可以做同样的动作,那么我们可以
看一看下面这个功能:
$gawk'$1=="-rwxr-xr-x"{sum=sum+$5}END{printsum}'list
15041
这个是什么意思?对于所有属性是755的文件,让gawk对第五栏的数字求和。第五栏我
们可以看到就是文件的长度,因此这个命令将显示所有属性为755的文件的总共的长度。
$n是gawk中非常重要的概念,它用来表示文本串的分栏。缺省的情况下,gawk将输入
字符串(从文件中读入的每一行)按照分割的空格分成若干个字段,每个字段作为一个
变量,例如有一行
mynameis3thtest
那么,在awk读入这一行之后,就产生了$1到$5变量,其中$1="my",$2="is",………
,最后$5="test"。另外还有一个特殊的变量$0,它表示整个输入行,也就是这个字符串
"mynameistest"。另外还有一个特殊的变量NF,它表示当前行的字段的个数,在现在
的情况下,NF应该等于5。
在某些特殊的情况下,你可能需要改变分割符的定义,这可以通过对FS赋值来完成,
例如FS=","将分割符定义为都号而不是缺省的空格。
在一般情况下,gawk可以从命令文件中获得模式/动作,命令文件的格式很简单,就是
直接将应该写在命令行上的模式/动作对写在文件里面,每个对构成一行,模式可以有两
种,一种是模式匹配,也就是我们在前面解释的正则表达式,如果使用正则表达式,那
么需要用两个/把它们夹在一起,例如/[A-Z]/表示正则表达式[A-Z]。
另一种模式是比较指令,比较指令可以用比较操作符和逻辑运算符来构成,常用的比
较操作符有:
==等于<=不大于~按照正则表达式匹配
<小于>=不小于!~按照正则表达式不匹配
>大于!=不等于
逻辑运算符有
&&和||或!非()括号
设定了模式后,就可以设置对应的动作了,在gawk中,动作必须用花括号括起来。ga
wk能完成的动作并不多,毕竟它是一种报告分析语言。一般情况下,只要熟悉print和p
rintf命令就足够了,print命令的格式非常简单:
printitem1,item2,…………
输出时,每个项目输出一栏,中间用空格分开。一个print后面不跟着任何变量会导致
gawk显示当前的输入行($0)。如果要输出一个字符串,使用引号把它括起来,特别是
如果要输出一个空行,使用print""。这里是一个例子,它将list文件的头两栏输出:
gawk'{print$1,$2}'list
由于输入的文本文件内容有多行,你在命令栏中设计的模式/动作会对每一行执行一次
。就是:
total2164
drwxr-xr-x3
-rw-r--r--1
-rw-r--r--1
-rw-r--r-1
…………………
如果你要精确地控制输出,也可以使用printf命令,这个命令的格式是:
printfformat,item1,item2,...
format参数就是C语言里面的格式控制符,例如%c,%d,%f等等。在%与格式控制
字母之间可加入modifier,modifier是用来进一步控制输出的格式。可能的modifie
r如下所示:
'-'使用在width之前,指明是向左靠齐。如果'-'没有出现,则会在被指定的
宽度向右靠齐。例如:
printf"%-4S","foo"会印出'foo'。
'width'这一个数字指示相对应的栏位印出时的宽度。例如:
printf"%4s","foo"会印出'foo'。
width的值是一个最小宽度而非最大宽度。如果一个item的值需要的宽度
比width大,则不受width的影响。例如printf"%4s","foobar"将印出'foobar'。
'.prec'此数字指定印出时的精确度。它指定小数点右边的位数。如果是要印出一个
字串,它指定此字串最多会被印出多少个字符。
作为一种脚本语言,gawk允许使用变量,定义变量非常简单,就是直接用等号对它赋
值。为了在gawk程序的开始处对变量赋值,gawk专门提供了BEGIN语句,这个语句将在所
有行被读入之前执行,而且只执行一次,通常用它来执行初始化命令,例如
BEGIN{sum=0;count=0;average=0.0;}
对于变量可以使用数学表达式进行运算,运算符包括常见的加减乘除算符,以及^(乘
方),%(取余)和著名的++,--。不过注意gawk在做除法的时候总是使用浮点除法,除了
取余算符%。
函数
另外,gawk包含下列函数:
数学函数
atan2(x,y)y/x的正切
cos(x)余弦函数
sin(x)正弦函数
int(x)取整
log(x)取自然对数
exp(x)指数函数
rand(x)生成一个0到1之间的随机数
srand()初始化随机数发生器
systime()返回从1970年1月1日0:00到当前时间的秒数
sqrt(x)取x的平方根
字符串函数
index(string1,string2)
它会在string1里面,寻找string2第一次出现的地方,返回值是字串string2出
现在字串string1里面的位置。如果找不到,返回值为0。
例如:
printindex("peanut","an")
会印出3。
length(string)
string字符串的长度
例如:
length("abcde")
是5。
match(string,regexp)
match函数会在字串string里面,寻找符合regexp的最长、最靠左边的子字
串。返回值是regexp在string的开始位置,即index值。这个函数会设定内部变量
RSTART等於index,内部变量RLENGTH等於符合的子串个数。如果不符合,则会设定
RSTART为0、RLENGTH为-1。
sprintf(format,expression1,...)
跟C语言的sprintf差不多。
例如:
sprintf("pi=%.2f(approx.)',22/7)
传回的字串为"pi=3.14(approx.)"
sub(regexp,replacement,target)
在字串target里面,寻找符合regexp的最长、最靠左边的地方,并且以字串
replacement代替最左边的regexp。
例如:
str="water,water,everywhere"
sub(/at/,"ith",str)
结果字串str会变成
"wither,water,everywhere"
gsub(regexp,replacement,target)
gsub与前面的sub类似。在字串target里面,寻找符合regexp的所有地方
,以字串replacement代替所有的regexp。
例如:
str="water,water,everywhere"
gsub(/at/,"ith",str)
结果字串str会变成
'wither,wither,everywhere"
substr(string,start,length)
传回字串string的子字串,这个子字串的长度为length个字符,从第start
个位置开始。
例如:
substr("washington",5,3)
传回值为"ing"
如果length没有出现,则传回的子字串是从第start个位置开始至结束。
例如:
substr("washington",5)
传回值为"ington"
tolower(string)
将字串string的大写字母改为小写字母。
例如:
tolower("MiXeDcAsE123")
传回值为"mixedcase123"
toupper(string)
将字串string的小写字母改为大写字母。
例如:
toupper("MiXeDcAsE123")
传回值为"MIXEDCASE123"
其他函数
system(command)
此函式允许使用者执行作业系统的指令,执行完毕後将回到gawk
程式。
例如:
BEGIN{system("ls")}
控制流
在gawk命令脚本中可以使用控制流,主要是if,for,while等语句,用法和C语言相当
类似:
if(condition)then-body[elseelse-body]
如果condition为真(true),则执行then-body,否则执行else-body。
举一个例子如下:
if(x%2==0)
print"xiseven"
else
print"xisodd"
while(condition)
body
while语句测试condition表达式。假如condition为真则执行body的语句。一次
执行完後,会再测试condition,假如condition为真,则body会再度被执行。这个
过程会一直被重复直到condition不再是真。如果condition第一次测试就是伪(fals
e),则body从没有被执行。
下面的例子会印出每个输入行的前三个栏位。
gawk'{i=1
while(i<=3){
print$i
i++
}
}'
do
body
while(condition)
这个doloop执行body一次,然後只要condition是真则会重复执行body。即使
开始时condition是伪,body也会被执行一次。
下面的例子会印出每个输入记录十次。
gawk'{i=1
do{
print$0
i++
}while(i<=10)
}'
for(initialization;condition;increment)
body
此叙述开始时会执行initialization,然後只要condition是真,它
会重复执行body与做increment。
下面的例子会印出每个输入记录的前三个栏位。
gawk'{for(i=1;i<=3;i++)
print$i
}'
break会跳出包含它的for、while、do-while循环的最内层。
下面的例子会找出任何整数的最小除数,它也会判断是否为质数。
gawk'#findsmallestdivisorofnum
{num=$1
for(div=2;div*div<=num;div++)
if(num%div==0)
break
if(num%div==0)
printf"Smallestdivisorof%dis%d/n",num,div
else
printf"%disprime/n",num}'
continue使用于for、while、do-while循环内部,它会跳过循环体的剩余部分
,立刻进行下一次循环的执行。
下面的例子会印出0至20的全部数字,但是5并不会被印出。
gawk'BEGIN{
for(x=0;x<=20;x++){
if(x==5)
continue
printf("%d",x)
}
print""
}'
next语句强迫gawk立刻停止处理目前的行而继续下一个输入行。
exit语句会使得gawk程式停止执行而跳出。然而,如果END出现,它会去执
行END的actions。
自定义函数
你可以定义自己的函数,其格式是
functionname(parameter-list){
body-of-function
}
name是所定义的函数名字。parameter-list是函数的变量列表。变量间使用逗号分
开。
函数可以在程序的任何地方定义,不过习惯上总是定义在程序的开头部分。
下面这个例子,会将每个记录的第一个栏位之值的平方与第二个栏位之值的平方加
起来。
{print"sum=",SquareSum($1,$2)}
functionSquareSum(x,y){
sum=x*x+y*y
returnsum
}
如果你熟悉任何编程语言,那么掌握awk都是很轻松的事情,如果你不喜欢它,那么你
可以参考我们下面介绍的perl。