新闻  |   论坛  |   博客  |   在线研讨会
Unix/Linux 平台任务的自动化
wxy_88kl | 2008-03-18 10:15:10    阅读:1390   发布文章

 
转载自:水木清华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"
  
  $b=@name[0];跟上一句是一样的
  
  $name[0,2]=["help","so"];现在@name等于[“help","marry","so"]
  
  @name[0,2]==@name[2,0];交换0,2元素
  
  数组的大小不是固定的,你可以动态地添加数组元素,例如
  
  $name[3]="app";增加一个元素
  
  直接访问数组名字将得到数组中元素的个数,例如:
  
  $count=@name;将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")也是可以使用的,不过这就等

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
漫天皆白, 雪里行军情更迫。 头上高山, 风卷红旗过大关。 此行何去?
推荐文章
最近访客