Unix/Linux 平台任务的自动化
转载自:水木清华BBS
本章要点:
本章介绍用来替代shell脚本的工具,如TCL和perl。
本章具体包括以下内容。
TCL/expect的使用
awk语言的基本知识
perl语言的基本知识
11.1 TCL和expect
TCL是一种类似shell脚本的语言,你可以使用它来完成许多操作。不过,我介绍它的
主要原因是expect是从它发展出来的。如果你想要写一个能够自动处理输入输出的脚本
(如向用户提问并且验证密码)又不想面对C或者Perl,那么expect是你的唯一选择。
11.1.1 TCL语言
要使用TCL,你必须先安装这个程序:
% rpm -q tcl
tcl-8.0.5-30
TCL语言可以用交互式或者脚本的方式执行,要使用交互式的TCL环境,只要输入
$ tclsh
%
出现的"%"符号是TCL的提示符,然后就可以使用TCL命令的。
如果你要使用脚本方式的TCL,首先把你的脚本写成一个文本文件,例如test.tcl,然
后执行
$ tclsh test.tcl
在tcl脚本中,每一行或者是一个命令行,或者是一个注释。注释行必须以#符号开头
,而命令行最好以分号结束,虽然不一定要这样做,但是这样做可以免去不少麻烦。
变量
在tcl中,有两种基本类型的变量,即标量和数组。标量就是一般的数字或者字符串变
量,可以用set语句定义同时赋值:
% set i 1
1
字符串应该用引号括起来:
% set str "test"
'test'
要输出一个标量的内容,使用put语句:
% puts $str
test
$用来说明str是一个变量。puts函数在标准输出显示变量的内容。
数组也可以用set语句定义,实际上,tcl中建立数组只是单个建立数组的元素。例如
,
% set arr(1) 0
0
% set arr(2) 1
1
这样就建立了一个两个元素的数组arr。在TCL中,不存在相当于数组边界这样的东西
,例如
% set arr(100) to
to
这时数组中实际只存在arr(1),arr(2)和arr(100),这是和C语言不同的地方。用arr
ay size命令可以返回数组的大小:
% array size arr
3
访问数组的方法和访问标两实际是一样的,例如:
% puts $arr(100)
to
可以用同样的方法创建多维数组。
要使用数组中的所有元素,需要使用一种特殊的便利方式。首先要启动startsearsh:
% array startsearch arr
s-1-arr
这里返回了一个搜索id,你可以把它传递给某个变量,因为以后还要使用它进行进一
步的搜索:
% set my_id [array startsearch arr]
s-1-arr
现在my_id的内容是s-1-arr,然后,就可以搜索arr的内容了:
% array nextelement arr $my_id
whi
这里的array nextelement返回的是什么?可能有点出乎你的意料,是arr数组的下标
,再执行一次array nextelement命令又会找出另外一个下标:
% array nextelement arr $my_id
4
这样遍历下去,可以找出arr数组的所有下标,而知道下标之后,就可以用$arr(4)之
类的方式访问arr的内容了。当遍历完成之后,array nextelement命令将简单地返回:
% array nextelement arr $my_id
%
这时就可以停止遍历过程了,如果你想确认遍历是否完成,可以使用array anymore命
令:
% array anymore arr $my_id
0
返回0说明遍历已经完成。
串处理
TCL中可以进行一般的串处理过程,这可以使用string命令和append命令,append命令
将某个字符串加到另外一个字符串的后面:
% set str1 "test "
test
% set str2 "cook it"
cook it
% append str1 $str2 " and other"
test cook it and other
string命令可以执行字符串的比较,删除和查询,其格式是 string [参数] string1
[string2]
参数可以是下面的命令之一:
compare 按照字典顺序对字符串进行比较,根据相对关系返回-1,0或者+1。
first 返回string2中第一次出现string1的位置,如果失败,返回-1。
last 返回string2中最后一次出现string1的位置,如果失败,返回-1
trim 从string1中删除开头和结尾的出现在string2中的字符
trimleft 从string1中删除开头的出现在string2中的字符。
trimright 从string1中删除结尾的出现在string2中的字符
下面几个用在string中的参数不需要string2变量:
length 返回tring1的长度
tolower 返回将string1全部小写化的串
toupper 返回将string1全部大写化的串
运算
TCL的运算方式比较别扭,它使用expr命令作为计算符号,其用法类似C语言的+=和/=
,例如,
% set j [expr $i/5]
1
注意TCL会自动选择整数或者浮点计算:
% set l [ expr $i /4.0]
1.25
% set l [ expr $i /4]
1
在TCL里面可以使用+ - * /和%作为基本运算符,另外通常还包括一些数学函数,如a
bs,sin,cos,exp和power(乘方)等等。
另外,还有一个起运算符作用的命令incr,它用来对变量加一:
% set i 1
1
% incr i
2
流程控制
tcl支持分支和循环。分支语句可以使用if和switch实现。if语句的和C语言类似,如
if { $ x < 0 } {
set y 10;
}
注意判断子句也需要使用花括号。
与C语言一样,tcl的if语句也可以使用else和elseif。
switch语句的用法有点类似这样:
switch $x {
0 { set y 10;}
10 { set y 100;}
20 { set y 400;}
}
与C的switch语句不同,每次只有符合分支值的子句才被执行。
循环命令主要由for,foreach和while构成,而且每一个都可以使用break和continue
子句。
for语句的格式有点类似这样:
for { set i 0} {$i < 10} { incr i} {puts $i}
将会输出从1到9的整数。
如果用while循环,这个句子可以写成
while {$i < 10 } {
puts $i;
incr i;
}
foreach是对于集合中的每一个元素执行一次命令,大致的命令格式是
foreach [变量] { 集合 } {
语句;
}
例如
% foreach j { 1 3 5} {
put $j;
}
1
3
5
函数
如同在一般的编程语言里面一样,在tcl里面也可以定义函数,这是通过proc命令实现
的:
proc my_proc {i}{
puts $i;
}
这样就定义了一个名字叫proc的函数,它只是在终端显示输入变元的内容。
要使用这个函数,简单地输入它的名字:
% my_proc { 5 }
5
如果变元的数目是0,只要使用空的变元列表,例如 proc my_proc {} {语句;}
尽管tcl还可以处理更复杂的过程,但是我们不再介绍了,例如文件的读写以及tk图形
语言,因为我们处理tcl的主要目标就是理解expect,对于更复杂的编程工作,我们建议
你使用perl。
11.1.2 expect
expect是建立在tcl基础上的一个工具,它用来让一些需要交互的任务自动化地完成。
我们首先从一个简单的例子开始,如同在这一节一开始就提到的,我们想设置一个自动
的文件下载程序。
我们看一看这样的一个例子脚本:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect "Name"
send "ftp\r"
expect "Password:"
send "nothing\r"
expect "apply"
send "cd /pub/UNIX/Linux/remoteX\r"
expect "successful."
send "bin\r"
expect "set to I"
send "get exceed5.zip\r"
expect "complete."
send "quit\r"
这个是什么意思?呵呵,就是个自动下载程序。第一行说明这个程序应该调用/usr/b
in/expect去执行,然后的就是expect命令。
察看expect的手册页面(man expect)可以得到一个很长的expect说明,可惜其中关于
expect的语法仍然介绍的不够。一般来说,expect主要用在需要自动执行人机交互的过
程中,例如fsck程序,这个程序会不断地提问"yes/no",像这样的命令就可以用expect
来完成。
spawn语句在expect脚本中用于启动一个新的进程,在我们的程序中,spawn ftp 202
.199.248.11就是去执行ftp程序,接下来,就是expect和send的指令对了。
每一对expect和send指令代表一个信息/回应。如果这样说不好理解的话,那么可以看
一看ftp的具体执行过程:
ftp 202.199.248.11
Connected to 202.199.248.11.
220 mail.asnc.edu.cn FTP server (BeroFTPD 1.3.3(3) Sun Feb 20 15:52:49 CST
2000.
Name (202.199.248.11:wanghy):
显然,一旦连接成功,服务器会返回一个Name(202.199.248.11:wanghy):的字符串来
要求客户给出用户名。expect语句简单地在返回信息中查询你给出的字符串,一旦成功
就执行下面的命令,现在,expect " Name"已经成功地找到了Name字符串,接下来可以
执行send命令了。
send命令比expect命令更简单,它简单地向标准输入提交你设定的字符串,现在设置
为send "ftp\r"表示等到登录信息之后就给出一个输入ftp回车,也就是标准的登录过
程。
下面的行与这些行完全一样,只是机械地等待服务器的回应,并且提交自己的输入。
要使用这个expect脚本,你只需要将它设置为可执行的属性,然后执行它,expect就
会执行你需要的服务。
由于expect是tcl的扩展,所以你在expect文件中可以象tcl脚本一样设置变量和程序
流程。
现在我们看一看我们还能够如何改进我们的expect脚本。ftp命令可能会失败,比如远
端的机器可能会无法提供服务,或者在启动ftp命令时本地机器发生问题。为了处理这一
类的问题,我们可以使用expect的timeout选项来设置超时的话expect脚本自动退出:
#! /usr/bin/expect
spawn ftp 202.199.248.11
expect {
timeout exit
Connect
}
………………
注意这里面使用的花括号。它的含义是使用一组并列表达式。使用并列表达式的主要
原因是这样:如果使用下面的指令对:
expect timeout
exit
那么由于expect脚本是顺序执行的,那么当程序执行到这个expect的时候就会阻塞,
所以程序会一直等待到timeout然后退出。并列表达式则是相当于switch的行为,只要列
出的几项内容有一项得到满足,expect命令就得到满足,于是程序可以正常执行。上面
的脚本表示,如果连接ftp的时候发生了超时,那么就退出,否则,一旦发现Connect应
答,说明服务器已经正常了,那么就可以继续运行了。
我们可以看看用tcl能够对我们的expect脚本提供什么帮助。我们可以设置让expect脚
本不断地连接远端服务器的服务,直到正常建立连接开始,为此,我们可以把建立连接
的命令放在一个循环里面,并且根据回应的不同自动选择重新输入命令还是继续执行:
spawn ftp
while {1} {
expect "ftp>"
send "o 202.199.248.11\r"
expect {
"Connected" break
"refused" { sleep 10} ;
}
}
这里使用了我们在tcl语言中讲到的while和break命令,熟悉C的读者应该很容易看出
它的行为:不断地等待ftp>提示符,在提示符下面发送连接远端服务器的命令,如果服
务器回应是refused(连接失败),就等待10秒钟,然后开始下一次循环;如果是Conne
cted,那么就跳出循环执行下面的命令。sleep是expect的一个标准命令,表示暂停若干
秒钟。
expect还支持许多更复杂的进程控制方式,如fork,disconnect等等,你可以从手册
页面中得到详细的信息。另外,各种tcl运算符和流程控制命令,包括tcl函数也可以使
用。
有些读者可能会问,如果expect执行的话是否控制台输入不能使用了,答案是否定的
。expect命令运行时,如果某个等待的信息没有得到,那么程序会阻塞在相应的expect
语句处,这时,你在键盘上输入的东西仍然可以正常地传递到程序中去,其实对于那些
expect处理的信息,原则上你输入的内容仍然有效,只是expect的反映太快,总是抢在
你的前面“输入”就是了。知道了这一点之后,你就可能写一个expect脚本,让expect
自动处理来自fscki的那些恶心的yes/no选项(我们介绍过,这些yes/no其实完全是多余
的,正常情况下你除了选择yes之外什么也干不了)。
缺省下,expect在标准输出(你的终端上)输出所有来自应用程序的回应信息,你可
以用下面的两个命令重定向这些信息:
log_file [文件名]
这个命令让expect在你设置的文件中记录输出信息。必须注意,这个选项并不影响控
制台输出信息,不过如果你通过crond设置expect脚本在半夜运行的话,你就确实可能需
要这个命令来记录各种信息了。例如:
log_file expect.log
log_user 0/1
这个选项设置是否显示输出信息,设置为1时是缺省值,为0 的话,expect将不产生任
何输出信息,或者说简单地过滤掉控制台输出。必须记住,如果你用log_user 0关闭了
控制台输出,那么你同时也就关闭了对记录文件的输出。
这一点很让人困扰,如果你确实想要记录expect的输出却不想让它在控制台上制造垃
圾的话,你可以简单地把expect的输出重定向到/dev/null:
./test.exp > /dev/null
你可以象下面这样使用一对fork和disconnect命令。expect的disconnect命令将使得
相应的进程到后台执行,输入和输出被重定向到/dev/null:
if [fork]!=0 exit
disconnect
fork命令会产生出一个子进程,而且它产生返回值,如果返回的是0,说明这是一个子
进程,如果不为0,那么是父进程。因此,执行了fork命令之后,父进程死亡而子进程被
disconnect命令放到后台执行。注意disconnect命令只能对子进程使用。
11.2 awk和文件的处理
UNIX里面充斥着各种记录文件和类似的东西。对文本文件的处理是系统管理员每天重
要的工作,例如从系统记录中查找重要的内容,或者对某种程序的输出进行统计等等。
我们将介绍常用的一个处理程序,即gawk。
11.2.1 grep和正则表达式
让我们首先从grep命令开始。这个命令大家应该很熟悉了,它用来在文件中查找一个
字符串。不过,实际上,grep的处理功能要强大和复杂的多。
grep 命令的语法是
grep [模式] [文件名]
如果没有给出文件名,就缺省使用标准输入。grep每次读取一行,并且和给出的模式
进行匹配,如果成功就把这一行会显,例如:(粗体的是我们输入的内容)
$ grep test
close
test my hand
test my hand
grep的“模式”也称为正则表达式,可以由各种基本的正则表达式元素构成。正则表
达式元素主要包括下面几种:
字符串 匹配任何字符串,例如grep test表示在标准输入中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 |grep sendmail,另一种是直接在文件中搜索对应的字符串。
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查询的时候可以使用通配符代表多个文件,例如,grep start * -r将在当前目
录以及所有子目录的所有文件中查询start字符串。
11.2.2 gawk的使用方法
gawk是awk的一个实现,awk是一种用来处理报告等文本文件的脚本语言。不过,我们
介绍这个产品的主要目标是用它来处理各种程序的记账文件。对于复杂的脚本,还是用
Perl比较合适。
gawk 的主要功能是针对档案的每一行搜寻指定的 模式。,每当找到一个匹配的模式
,gawk就会去执行你设定的动作。按照这个方式, gawk 依此方式处理输入档案的每一
行直到输入档案结束。如果对于某个模式没有设置对应的动作,gawk将直接将这个行显
示出来。
为了使用gawk,你通常必须先写一个awk脚本,除非模式/动作非常简单,可以在一行
上完成。我们用一个例子来解释gawk的基本用法,首先产生一个目录列表文件:
ls –l /etc > list
现在list的内容有点像这样:
total 2164
drwxr-xr-x 3 root root 4096 Feb 15 22:55 CORBA
-rw-r--r-- 1 root root 2045 Sep 24 1999 DIR_COLORS
-rw-r--r-- 1 root root 17 Mar 25 19:59 HOSTNAME
…………
现在我们选择一个最简单的例子,简单地查找所有属性是drwxr-xr-x的目录文件:
gawk '/drwxr-xr-x/ {print $0}' list
将输出所有这样的目录。
这个例子看上去没有什么实际用处,因为用grep也可以做同样的动作,那么我们可以
看一看下面这个功能:
$ gawk '$1=="-rwxr-xr-x" {sum=sum+$5} END {print sum}' list
15041
这个是什么意思?对于所有属性是755的文件,让gawk对第五栏的数字求和。第五栏我
们可以看到就是文件的长度,因此这个命令将显示所有属性为755的文件的总共的长度。
$n是gawk中非常重要的概念,它用来表示文本串的分栏。缺省的情况下,gawk将输入
字符串(从文件中读入的每一行)按照分割的空格分成若干个字段,每个字段作为一个
变量,例如有一行
my name is 3th test
那么,在awk读入这一行之后,就产生了$1到$5变量,其中$1="my",$2="is",………
,最后$5="test"。另外还有一个特殊的变量$0,它表示整个输入行,也就是这个字符串
"my name is test"。另外还有一个特殊的变量NF,它表示当前行的字段的个数,在现在
的情况下,NF应该等于5。
在某些特殊的情况下,你可能需要改变分割符的定义,这可以通过对FS赋值来完成,
例如FS=","将分割符定义为都号而不是缺省的空格。
在一般情况下,gawk可以从命令文件中获得模式/动作,命令文件的格式很简单,就是
直接将应该写在命令行上的模式/动作对写在文件里面,每个对构成一行,模式可以有两
种,一种是模式匹配,也就是我们在前面解释的正则表达式,如果使用正则表达式,那
么需要用两个/把它们夹在一起,例如/[A-Z]/表示正则表达式[A-Z]。
另一种模式是比较指令,比较指令可以用比较操作符和逻辑运算符来构成,常用的比
较操作符有:
== 等于 <= 不大于 ~ 按照正则表达式匹配
< 小于 >= 不小于 !~ 按照正则表达式不匹配
> 大于 != 不等于
逻辑运算符有
&& 和 || 或 ! 非 ()括号
设定了模式后,就可以设置对应的动作了,在gawk中,动作必须用花括号括起来。ga
wk能完成的动作并不多,毕竟它是一种报告分析语言。一般情况下,只要熟悉print和p
rintf命令就足够了,print命令的格式非常简单:
print item1,item2,…………
输出时,每个项目输出一栏,中间用空格分开。一个print后面不跟着任何变量会导致
gawk显示当前的输入行($0)。如果要输出一个字符串,使用引号把它括起来,特别是
如果要输出一个空行,使用print ""。这里是一个例子,它将list文件的头两栏输出:
gawk '{print $1,$2}' list
由于输入的文本文件内容有多行,你在命令栏中设计的模式/动作会对每一行执行一次
。就是:
total 2164
drwxr-xr-x 3
-rw-r--r-- 1
-rw-r--r-- 1
-rw-r--r-1
…………………
如果你要精确地控制输出,也可以使用printf命令,这个命令的格式是:
printf format, 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。
例如:
print index("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("MiXeD cAsE 123")
传回值为"mixed case 123"
toupper(string)
将字串string的小写字母改为大写字母。
例如:
toupper("MiXeD cAsE 123")
传回值为"MIXED CASE 123"
其他函数
system(command)
此函式允许使用者执行作业系统的指令,执行完毕後将回到 gawk
程式。
例如:
BEGIN {system("ls")}
控制流
在gawk命令脚本中可以使用控制流,主要是if,for,while等语句,用法和C语言相当
类似:
if (condition) then-body [else else-body]
如果 condition 为真(true),则执行 then-body,否则执行 else-body。
举一个例子如下:
if (x % 2 == 0)
print "x is even"
else
print "x is odd"
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)
这个 do loop 执行 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 '# find smallest divisor of num
{ num=$1
for (div=2; div*div <=num; div++)
if (num % div == 0)
break
if (num % div == 0)
printf "Smallest divisor of %d is %d\n", num, div
else
printf "%d is prime\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。
自定义函数
你可以定义自己的函数,其格式是
function name (parameter-list) {
body-of-function
}
name 是所定义的函数名字。 parameter-list 是函数的变量列表。变量间使用逗号分
开。
函数可以在程序的任何地方定义,不过习惯上总是定义在程序的开头部分。
下面这个例子,会将每个记录的第一个栏位之值的平方与第二个栏位之值的平方加
起来。
{print "sum =",SquareSum($1,$2)}
function SquareSum(x,y) {
sum=x*x+y*y
return sum
}
如果你熟悉任何编程语言,那么掌握awk都是很轻松的事情,如果你不喜欢它,那么你
可以参考我们下面介绍的perl。
11.3 Perl
Perl是从awk发展起来的,它由Larry Wall在1986年发明。它是一种功能强大的编程语
言,而且可以在许多平台上使用。实际上,你完全可以将Perl作为一种标准编程语言(
而不是脚本语言)来使用,笔者非常喜欢它,并且建议所有不想学习C语言的UNIX管理员
应该掌握Perl的基本编程技术。目前,常用的版本是perl 5,几乎所有的Linux发行版本
都会包含它,缺省时,linux的perl 5安装在/usr/bin下,命令是/usr/bin/perl.
11.3.1 基本语法
perl的语法介于C和basic之间,一个perl程序由若干行组成,使用的时候由perl解释
程序解释执行。每个完整的行都应该用分号结尾。
Perl的基本语法是这样的:
① 变量和运算符
在perl中,所有变量都不需要提前声明。一旦对某个变量赋值,就自动产生了这个变
量。perl的变量有普通变量,数组和关联数组三种。普通变量就是数值和字符串,要声
明一个普通变量,在变量名字前面加上$,例如
$string1="aaa";
$test=5;
$u=1.33;
同样,访问变量内容也需要使用$符号。
数组用@字符标志,如
@name1=("tom","marry","john");
$b=$name[0]; $b现在等于"tom"
];跟上一句是一样的
$name[0,2]=["help","so"];现在@name等于[“help","marry","so"]
@name[0,2]==@name[2,0];交换0,2元素
数组的大小不是固定的,你可以动态地添加数组元素,例如
$name[3]="app";增加一个元素
直接访问数组名字将得到数组中元素的个数,例如:
;将name的元素个数存放到$count变量中。
关联数组是一种特殊的数组,每个元素都由一对元素构成。或者说,关联数组是一种
下标不是整数的数组,要声明一个关联数组,使用%符号,例如:
%arr=(1,"one",2,"two",3,"three",4,"four");
这时可以用前面的值(key)来索引后面的值:
$one=$arr{1};这时$one等于"one"
注意关联数组的访问方式,是使用$关联数组名字[索引号]。
你可以把关联数组看成数据库的一种实现。与一般的数组一样,其大小也可以动态调
节:
$arr{5}="five";增加一对数据。
可以将关联数组简单地变成普通数组,例如
@X=%arr;现在@X的内容是X[0]="1",X[1]="one",……………
perl的运算符与C语言以及我们介绍的gawk很相似,包括普通的+-*/%以及来自C语言的
逻辑运算符&&(和),||(或),等等,下面是一个列表:
+ - * / 四则运算,注意perl的除法是浮点除法
$a % $b a对b取余数,例如3%2的结果是1
$1 .. $2 区段运算符,这个算符取出$1和$2中间的所有值,例如1..9返回一个表
1,2,………9。通常用这个命令初始化一个数组,例如:@dec=1..9;@oth=(1..26,'A
'..'Z')等等。
= 赋值算符
> < >= <= == !=
这几个算符是数字之间的比较算符。
perl中没有专门的boolean型变量,而是象C语言一样认为所有不为零的量为真值,而
0或者空字符串为假。与C语言类似,Perl支持以下的逻辑运算符:
&& 与 || 或 ! 非
同样,perl也支持位运算:
& 与 || 或 ^ 异或
还有就是与C语言相似的运算符使用方式,如
$i+=$j; 等效于$i=$i+$j,同样还可以使用$i-=5;$i&=12。这样的算式
$i++; 等效于C语言的++,将i加一,++$i,$i--,--$i都是可以使用的。
除了上面的标准算式之外,perl支持字符串运算,首先是字符串之间的比较命令:
$str1 gt $str2 $str1大于$str2
$str1 lt $str2 $str1小于$str2
$str1 ge $str2 $str1不小于$str2
$str1 le $str2 $str1不大于$str2
$str1 eq $str2 $str1等于$str2
$str1 ne $str2 $str1不等于$str2
$str1 cmp $str2 根据$str1是大于,等于还是小于$str2,返回1,0或者-1。
上面的字符串比较都是使用字典顺序,即ASCII码的顺序。
另一个非常有用的运算符是点号运算符,这个运算符用于把两个字符串连接成一个,
例如:
$str1="string1";
$str2="string2";
$string3=$str1.$str2;这时$string3等于"string1string2"
②基本语句和函数:
#
这个符号代表注释的开始。
print
显示字符串,写文件,如
print "hello\n";或者print "the var is $i","\n";注意变量名会自动地被替换成变
量值,除非你用一个\符号明确地告诉perl:
print "\$i is a str";
print FILE "hello\n";向FILE对应的文件写,FILE是一个文件句柄;
split 分割字符串,格式split(/模式/,$string);
例如$string="i:am:perl";
@list=split(/:/,$string);
#这时@list=("i","am","perl")
($a,$b,$c)=split(/:/,$string);
delete $ARRAY(key)
这个函数用于在关联数组中删除一对记录。例如,%arr=(1,”one”,2,”two”,3,”
three”); delete $arr(2);执行上述操作之后,%arr的内容变为(1,”one”,2,”two
”)。
keys(%ARRAY)
取出关联数组%ARRAY中所有的索引key。这个操作将返回一个数组。例如,对于上面的
%arr,执行@test=key(%arr)的结果是@test成为(1,2)。
values(%ARRAY)
取出关联数组%ARRAY中所有的value,同样返回一个数组,例如对于%arr,@test=val
ues(%arr)的结果是@test变成(”one”,”two”)。
reverse(@array)
把@array反转排列。例如@test=(1,2,5,3,10),@other=reverse(@test)的结果是@ot
her变成(10,3,5,2,1)。
sort(@array)
排序,注意这个排序是按照字符串的排序,例如@other=sort(@other)的结果是(1,
10,2,3,5)。
chop($string)
删除字符串的最后一个字符,通常用于去掉输入字符串中的回车符。
lenth($string)
取字符串长度
substr($string,offset,length)
取字符串子串,即从$string的offset偏移量处截取length长度的字符串作为子串返回
index($string,$substring)
在$string中查找$substring,成功的话,返回$substring在$string中的偏移量,如
果不存在就返回-1。
push(@array,$string)
在@array末尾加入$string
pop(@array)
删除@array的末尾元素并返回这个元素
shift(@array)
删除@array的开头元素并且返回这个元素
join($string,@array)
在@array中间加入$string并返回结果
grep(/pattern/,@array)
在@array中用正则方式查找符合条件的元素
hex($string)
将16进制转化为十进制
rand
产生随机数,注意应该先执行srand初始化随机数种子。
localtime
返回时间数组
die LIST
显示字符串并且退出程序
pack("格式”,LIST)
把一个LIST转换成指定的二进制格式,例如:$string=pack('C",65)这时$string等于
ASCII的65,即"A"
反引号
用反引号将某个字符串括起来的效果是使perl执行系统命令,这里使用的反引号是大
键盘最左边键,如`ls`执行ls命令。
③使用文件
在perl中使用文本文件非常简单,只要使用open和close打开和关闭文件:
open 打开文件
close 关闭文件
open函数的格式是open(Filehandle,$filename),这个操作将会打开$filename文件
,并且让Filehandle句柄指向打开的文件。如果失败,将返回false。缺省下,文件是以
只读的方式打开的。如果要打开名字为$filename文件用于输出,使用open(Filehandle
,">$filename")。想要在某个文件的后面追加内容,使用open(Filehandle,">>$filena
me")。当然,open(Filehandle,"<$filename")也是可以使用的,不过这就等于open(Fi
lehandle,"$filename")。
read 读文件,格式是read (Filehandle,$string,length),这函数从Filehandle指向
的文件中读取length个字符,存放到$string变量中。如果你要得到标准输入,使用STD
IN的句柄。
close (Filehandle)将关闭由open语句打开的文件。
除了用read语句的标准方法之外,还有一个经常用的方法:
$filecontent=;从句柄FILE指向的文件中读取一行,内容存入$filecontent变
量。如果你要从控制台读取一个字符串,使用$input=;就可以了。
下面是一个例子:
$filename="test";
open (FILE,"$filename")||die "can not open file!;
while($line={
print "$line";
}
close(FILE);
这个程序实际就是cat命令的perl语言实现,open命令打开当前目录下面的test文件,
并且把句柄返回到FILE变量,注意这一行的用法,Perl的||(或)运算是短路求值的,如
果open成功,那么返回一个非0的数,因此这算式无论如何都会为真,所以会跳过||后面
的东西;否则,如果open失败,perl就要对后面的东西执行一下,于是退出这个程序。
打开成功之后,perl会得到这个文件的句柄,下面的句子就是反复读取文件的每一行
并且显示出来,当文件读到末尾的时候,$line=将产生一个空字符串,于是whil
e循环结束。
与shell脚本语言类似,perl还有一些文件测试运算符
-t $file
如果$file这个文件可读,返回1,$file是文件名。
-w $file
如果$file可写,返回1
-x $file
如果$file可以执行,返回1
-e $file
如果$file存在,返回1
-o $file
如果用户是$file的拥有者,返回1
-s $file
返回$file文件的大小
-f $file
是否为正常文件
-T $file
是否文本
-B $file
是否二进制文件
-M $file
文件从更新到现在的日期数
④流程控制
perl支持与C语言很相似的流程控制语句:
if和if..else:
if语句的语法是
if(...){
clause;
}
与C语言不同,即使只有一行程序,if后面的花括号也不能省略,这一点也适用于后面
说的其他复合语句。
与C语言类似,也可以用else和elseif子句:
if(...){
clause1;}
else {
clause2;
}
或者
if (…){
...
}
elseif(…){
....
}
else{
...
}
另外,perl还支持unless语句:
unless(exp1){
clause1;
}
如果exp1不成立,就执行clause1子句。这个unless语句里面也可以使用else子句。实
际上,这就是一种否定形式的if……else语句。
while循环语句:
while的语法有两种,分别是将表达式放在循环首部和尾部,第一种形式是:
while(exp){
clause;
}
第二种形式是
do {
clause;
}
while(exp);
都是循环执行clause直到exp不成立,不过一个在循环头部判断exp表达式是否为真,
另一个是在循环尾部。
until循环
语法是until(exp){
clause;
}
反复执行clause直到exp成立。
for循环
for(初始化;继续条件;增量){
循环体;
}
这个for循环和C的一样,首先执行初始化语句,然后开始循环执行循环体,每次循环
都调用一次增量表达式,直到循环继续条件不再成立。例如
for ($1=0;$i<10;$i++){
print $i;
}
将显示出从0到9的所有整数。
foreach $variable(@array){
循环体;
}
这个类似于shell语言的foreach,它把@array的内容一条一条赋给$variable并执行里
面的语句。
跳出循环
有两个语句用来实现特殊控制:
last if 用在循环里,相当于break;
next if 相当于continue.
⑤文字处理运算
除了正常的字符串处理语句外,perl还支持一种文字处理运算方式,基本格式是$str
ing=~(文字处理模式)。这个文字处理模式是perl的主要优点之一,由于perl的文字处
理运算模式太强大了,这里只能介绍几个非常基本的形式。
首先解释一下pattern的概念.pattern一般是用两个/字符夹在一起的一些字符串, 用
来代表一些具有某些特点的字符串, 实际上,这个patternj基本上就是grep/egrep里面
的正则表达式,只是功能更丰富一点。常用的有:
任意字符串: 匹配该字符串
[0-9] 匹配所有数字字符
[a-z] 所有小写字母
[^0-9] 所有的非数字
[^a-z] 所有的非小写字母
[A-Z] 所有的大写字母
^ 字符串开头的字符
$ 字符串结尾的字符
\d 跟[0-9]一样
\D 非数字字符
\w 就相当于[a-zA-Z0-9]
\W 相当于[^a-zA-Z0-9]
\s 一个空白的字符
\S 非空白的字符
\d+ 一个相当于数字的字符串
\w+ 一个完全由数字或字符构成的字符串
\b 一个不由英文字母或者数字为边界的字符串
\B 一个由字母或数字为边界的字符串
a|b|c 符合a,b,c之一的字符串
/pattern/i i代表忽略大小写
[] 找寻符合[]内的字符,例如[abde]可以匹配a,b,d,e的任何一种
? {m} 正好是m个指定的字母
{m,n} 多于m少于n个指定的字符
\ 如果要引用一些在pattern中具有特殊意义的字符,使用\前缀
在perl的文本运算模式中,最常用的是=~或者!~运算符号和s,tr两个函数。=~表示匹配
,它用右边的模式来匹配左边的字符串,如果成功就得到一个true,!~正好相反,代表
不匹配,例如
$string="chmod711cgi";
if($string=~"/chmod/"){
print "Found chmod!\n";}
$string=~"/chmod/"这样的表达式询问是否/chmod/可以匹配$string,因为$string中
包含chmod,所以if语句会成功地执行。
tr是串转换函数,例如:
$string=~tr/a-z/A-Z/;
将字符串中的小写字母转换成大写.
s是串取代函数,例如:
$string=~s/a/A/;
把第一个a换成A,还可以加后缀g表示全程替换,例如:
$string=~s/1/A/g;
变换后,$string变成chmod7AAcgi.
注意tr和s的区别,tr的多替换一般是一对一的,或者说是字符的替换,而s是字符串的
替换,长度可以改变,例如如果$string的值是chmod711cgi,那么
$string=~s/1/ONE/g;
将变成chmod7ONEONEcgi.而
$string=~tr/1/ONE/;
将把$string变成chmod7oocgi。
还有一个很实用的功能是变数替换,例如:
$string="test24";
$string=~s/(\d+)/<$1>/;
这时,s首先搜索满足\d+(数字)的串,得到24,然后送入$1,接着再套上<>,结果是 "tes
t<24>".
⑥内置变量
除了用户定义的变量之外,perl还定义了一些内置变量,它们在perl中有特殊的意义
,用户不能修改它们的含义(有些可以赋值),下面是常用的内置变量:
@ARGV
这是个数组,它代表的是传递给perl程序的命令行开关,如@ARGV[0]是第一个参数,
@ARGV[1]是第二个等等。如果你的perl程序名字叫test,而你用test me other去调用它
,那么@ARGV[0]是”me”,@ARGV[1]是other,以此类推。
$_和$1,$2,………
这几个参数用于文本处理模式中的临时变量。举个例子来说,串匹配模式中:
$string="chmod711cgi"
$string=~/(\w+)\s+(\d+)/;
看上去这第二行代码什么也不做,因为它仅仅是个匹配语句。不过,实际上,由于pe
rl会把临时变量放进$n变量,所以它会修改$_和$1,………变量。在这个匹配模式中,
首先的\w+匹配一组字符,成功,得到chmod字符串,于是perl将它保存到$1;然后,\s
+匹配一组空白符号,失败;最后,\d+匹配一组数字,成功,得到711,perl将它保存到
$2。
如果在匹配模式中没有指明对那个串变量使用匹配模式,就使用$_进行匹配。
⑦自定义函数
Perl程序中允许定义自己的子程序。例如,下面的语句定义了一个子过程:
sub my_proc {
print “this is my subrouting\n”;
}
sub是子过程的说明,my_proc是过程名字,要调用这个过程,只要使用&符号,例如:
if($want==1){
&my_proc;
}
11.3.2 perl的使用
上面介绍了perl的基本语法。要使用perl,你当然可以在交互模式下使用,但是,一
般情况下,我们用perl是代替shell脚本完成自动化任务的。为此,我们需要把perl写成
程序来运行。写perl程序可以用任何文本编辑工具创建,一般总是设置其扩展名为.pl,
虽然实际上扩展名是不需要的。
要运行一个perl程序,有两种方法,一种是调用perl解释器读入perl源程序,例如,
我们已经写了一个perl程序,命名为test.pl,那么,可以用下面的命令执行它:
$ /usr/bin/perl test.pl
不过,在一般情况下,我们更喜欢在命令行下面直接敲入程序的名字执行它,为此,
可以在perl程序的头部加入这样的行:
#!/usr/bin/perl
注意#!符号,它告诉shell应该用什么程序来解释当前脚本,这里的定义是/usr/bin/
perl,这是缺省的的perl安装位置。如果你的perl可执行程序放在别的目录下,自己修
改这一行。这样,当shell读到这个文件的时候,就会自动启动perl来解释这个程序。
perl最常用的功能是用来写cgi程序,不过,我们不想涉及cgi程序的细节。相反,我
们介绍如何用perl进行日常管理,用perl程序代替书写晦涩的shell脚本。
让我们从一个我自己的例子开始,我经常要把一些txt文本直接转化成html文本,以便
做进一步的编辑和主页发布,其实这个操作非常简单,就是将回车和大于号,小于号都
换成对应的HTML标记,这可以用word或者netscape来完成,但是如果涉及的文件数目比
较多或者文件比较大,用这些软件就很困难了,所以我写了一个十分简单的perl脚本:
#!/usr/bin/perl
if((@ARGV[0] eq "")||(@ARGV[1] eq "")) {
print "convert inputfile outputfile\n";
exit;}
open (FILE1,"@ARGV[0]")||die"can not open source file!\n";
open (FILE2,">@ARGV[1]")||die "can not open taget file!\n";
while($line=){
$line=~s/</g;
$line=~s/>/>/g;
$line=~s/\^M//g;
$line=~s/\n/
/g;
print FILE2 $line;
}
close (FILE1);
close (FILE2);
这个程序简单得几乎象是DOS的批命令,首先检查参数是否定义,然后从输入文件中读
取行,把回车和两个尖括号换成对应的html标记,写入对应文件,任务就完成了。要使
用它,比如把test.txt转换成test.html,可以直接执行:
./convert.pl test.txt test.html
perl也可以完成更复杂的操作,最常见的功能扩展是进行网络编程,如直接使用电子
邮件,电子新闻服务等等。不过我们这里不能进一步介绍它的强大功能。对perl感兴趣
的朋友,可以进一步查阅有关书籍学习编程技术。
11.4 其他工具
还有很多自动化脚本工具,例如python等等。另外,对于一个职业的系统管理人员,
熟悉C语言的编程是很有好处的。
就我个人而言,我感到,如果你不想学习shell,那么了解expect和perl就足以对付一
般的系统管理工作。另外,还有一些威力强大的可编程工具,一个是我们刚才提到的py
thon,这是一种面向对象的脚本平台,如果你的大部分任务使用shell和expect,那么这
个东西可能很适合你使用。
另外一个非常有争议的产品是emacs。这个编辑工具是GNU计划的头号产物,随着发展
,它已经从原来一个文本编辑程序发展成为一个使用lisp宏控制,几乎可以做文本界面
下的一切事情的集成环境。反对它的理由主要是它的运行速度在低配置的机器上几乎无
法忍受,而且配置起来也十分困难。不论如何,使用emacs有时显得比较有专业特色,至
少是很有GNU的特色