管理脚本语言 |
用 vi 编辑器编辑一个 hello 文件如下:
#!/bin/bash
# This is a very simple example
echo Hello World
这样最简单的一个 BASH 程序就编写完了。这里有几个问题需要说明一下:
一,第一行的 #! 是什么意思
二,第一行的 /bin/bash 又是什么意思
三,第二行是注释吗
四,echo 语句
五,如何执行该程序
#! 是说明 hello 这个文件的类型的,有点类似于 Windows 系统下用不同文件后缀来表示不同文件类型的意思(但不相同)。Linux 系统根据 "#!" 及该字串后面的信息确定该文件的类型,关于这一问题同学们回去以后可以通过 "man magic"命令 及 /usr/share/magic 文件来了解这方面的更多内容。在 BASH 中 第一行的 "#!" 及后面的 "/bin/bash" 就表明该文件是一个 BASH 程序,需要由 /bin 目录下的 bash 程序来解释执行。BASH 这个程序一般是存放在 /bin 目录下,如果你的 Linux 系统比较特别,bash 也有可能被存放在 /sbin 、/usr/local/bin 、/usr/bin 、/usr/sbin 或 /usr/local/sbin 这样的目录下;如果还找不到,你可以用 "locate bash" "find / -name bash 2> /dev/null" 或 "whereis bash" 这三个命令找出 bash 所在的位置;如果仍然找不到,那你可能需要自己动手安装一个 BASH 软件包了。
第二行的 "# This is a ..." 就是 BASH 程序的注释,在 BASH 程序中从“#”号(注意:后面紧接着是“!”号的除外)开始到行尾的多有部分均被看作是程序的注释。的三行的 echo 语句的功能是把 echo 后面的字符串输出到标准输出中去。由于 echo 后跟的是 "Hello World" 这个字符串,因此 "Hello World"这个字串就被显示在控制台终端的屏幕上了。需要注意的是 BASH 中的绝大多数语句结尾处都没有分号。
如何执行该程序呢?有两种方法:一种是显式制定 BASH 去执行:
$ bash hello 或
$ sh hello (这里 sh 是指向 bash 的一个链接,“lrwxrwxrwx 1 root root 4 Aug 20 05:41 /bin/sh -> bash”)
或者可以先将 hello 文件改为可以执行的文件,然后直接运行它,此时由于 hello 文件第一行的 "#! /bin/bash" 的作用,系统会自动用/bin/bash 程序去解释执行 hello 文件的:
$ chmod u+x hello
$ ./hello
此处没有直接 “$ hello”是因为当前目录不是当前用户可执行文件的默认目录,而将当前目录“.”设为默认目录是一个不安全的设置。
需要注意的是,BASH 程序被执行后,实际上 Linux 系统是另外开设了一个进程来运行的。
简单变量
在 BASH 中变量定义是不需要的,没有 "int i" 这样的定义过程。如果想用一个变量,只要他没有在前面被定义过,就直接可以用,当然你使用该变量的第一条语句应该是对他赋初值了,如果你不赋初值也没关系,只不过该变量是空( 注意:是 NULL,不是 0 )。不给变量赋初值虽然语法上不反对,但不是一个好的编程习惯。好了我们看看下面的例子:
首先用 vi 编辑下面这个文件 hello2:
#!/bin/bash
# give the initialize value to STR
STR="Hello World"
echo $STR
在上面这个程序中我们需要注意下面几点:
一,变量赋值时,'='左右两边都不能有空格;
二,BASH 中的语句结尾不需要分号(";");
三,除了在变量赋值和在FOR循环语句头中,BASH 中的变量使用必须在变量前加"$"符号,同学们可以将上面程序中第三行改为 "echo STR" 再试试,看看会出什么结果。
四,由于 BASH 程序是在一个新的进程中运行的,所以该程序中的变量定义和赋值不会改变其他进程或原始 Shell 中同名变量的值,也不会影响他们的运行。
更细致的文档甚至提到以但引号括起来的变量将不被 BASH 解释为变量,如 '$STR' ,而被看成为纯粹的字符串。而且更为标准的变量引用方式是 ${STR} 这样的,$STR 自不过是对 ${STR} 的一种简化。在复杂情况下(即有可能产生歧义的地方)最好用带 {} 的表示方式。
BASH 中的变量既然不需要定义,也就没有类型一说,一个变量即可以被定义为一个字符串,也可以被再定义为整数。如果对该变量进行整数运算,他就被解释为整数;如果对他进行字符串操作,他就被看作为一个字符串。请看下面的例子:
#!/bin/bash
x=1999
let "x = $x + 1"
echo $x
x="olympic'"$x
echo $x
关于整数变量计算,有如下几种:" + - * / % ",他们的意思和字面意思相同。整数运算一般通过 let 和 expr 这两个指令来实现,如对变量 x 加 1 可以写作:let "x = $x + 1" 或者 x=`expr $x + 1`
在比较操作上,整数变量和字符串变量各不相同,详见下表:
对应的操作 整数操作 字符串操作
相同 -eq =
不同 -ne !=
大于 -gt >
小于 -lt <
大于或等于 -ge
小于或等于 -le
为空 -z
不为空 -n
比如:
比较字符串 a 和 b 是否相等就写作:if [ $a = $b ]
判断字符串 a 是否为空就写作: if [ -z $a ]
判断整数变量 a 是否大于 b 就写作:if [ $a -gt $b ]
更细致的文档推荐在字符串比较时尽量不要使用 -n ,而用 ! -z 来代替。(其中符号 "!" 表示求反操作)
BASH 中的变量除了用于对 整数 和 字符串 进行操作以外,另一个作用是作为文件变量。BASH 是 Linux 操作系统的 Shell,因此系统的文件必然是 BASH 需要操作的重要对象,如 if [ -x /root ] 可以用于判断 /root 目录是否可以被当前用户进入。下表列出了 BASH 中用于判断文件属性的操作符:
运算符 含义( 满足下面要求时返回 TRUE )
-e file 文件 file 已经存在
-f file 文件 file 是普通文件
-s file 文件 file 大小不为零
-d file 文件 file 是一个目录
-r file 文件 file 对当前用户可以读取
-w file 文件 file 对当前用户可以写入
-x file 文件 file 对当前用户可以执行
-g file 文件 file 的 GID 标志被设置
-u file 文件 file 的 UID 标志被设置
-O file 文件 file 是属于当前用户的
-G file 文件 file 的组 ID 和当前用户相同
file1 -nt file2 文件 file1 比 file2 更新
file1 -ot file2 文件 file1 比 file2 更老
注意:上表中的 file 及 file1、file2 都是指某个文件或目录的路径。
关于局部变量
在 BASH 程序中如果一个变量被使用了,那么直到该程序的结尾,该变量都一直有效。为了使得某个变量存在于一个局部程序块中,就引入了局部变量的概念。BASH 中,在变量首次被赋初值时加上 local 关键字就可以声明一个局部变量,如下面这个例子:
#!/bin/bash
HELLO=Hello
function hello {
local HELLO=World
echo $HELLO
}
echo $HELLO
hello
echo $HELLO
该程序的执行结果是:
Hello
World
Hello
这个执行结果表明全局变量 $HELLO 的值在执行函数 hello 时并没有被改变。也就是说局部变量 $HELLO 的影响只存在于函数那个程序块中。
BASH 中的变量与 C 语言中变量的区别
这里我们为原来不熟悉 BASH 编程,但是非常熟悉 C 语言的程序员总结一下在 BASH 环境中使用变量需要注意的问题。
1,BASH 中的变量在引用时都需要在变量前加上 "$" 符号( 第一次赋值及在For循环的头部不用加 "$"符号 );
2,BASH 中没有浮点运算,因此也就没有浮点类型的变量可用;
3,BASH 中的整形变量的比较符号与 C 语言中完全不同,而且整形变量的算术运算也需要经过 let 或 expr 语句来处理;
在 Linux 系统中:标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕(上面的 std 表示 standard)。在 BASH 中使用这些概念时一般将标准输出表示为 1,将标准错误输出表示为 2。下面我们举例来说明如何使用他们,特别是标准输出和标准错误输出。
输入、输出及标准错误输出主要用于 I/O 的重定向,就是说需要改变他们的默认设置。先看这个例子:
$ ls > ls_result
$ ls -l >> ls_result
上面这两个命令分别将 ls 命令的结果输出重定向到 ls_result 文件中和追加到 ls_result 文件中,而不是输出到屏幕上。">"就是输出(标准输出和标准错误输出)重定向的代表符号,连续两个 ">" 符号,即 ">>" 则表示不清除原来的而追加输出。下面再来看一个稍微复杂的例子:
$ find /home -name lost* 2> err_result
这个命令在 ">" 符号之前多了一个 "2","2>" 表示将标准错误输出重定向。由于 /home 目录下有些目录由于权限限制不能访问,因此会产生一些标准错误输出被存放在 err_result 文件中。大家可以设想一下 find /home -name lost* 2>>err_result 命令会产生什么结果?
如果直接执行 find /home -name lost* > all_result ,其结果是只有标准输出被存入 all_result 文件中,要想让标准错误输出和标准输入一样都被存入到文件中,那该怎么办呢?看下面这个例子:
$ find /home -name lost* > all_result 2>& 1
上面这个例子中将首先将标准错误输出也重定向到标准输出中,再将标准输出重定向到 all_result 这个文件中。这样我们就可以将所有的输出都存储到文件中了。为实现上述功能,还有一种简便的写法如下:
$ find /home -name lost* >& all_result
如果那些出错信息并不重要,下面这个命令可以让你避开众多无用出错信息的干扰:
$ find /home -name lost* 2> /dev/null
同学们回去后还可以再试验一下如下几种重定向方式,看看会出什么结果,为什么?
$ find /home -name lost* > all_result 1>& 2
$ find /home -name lost* 2> all_result 1>& 2
$ find /home -name lost* 2>& 1 > all_result
另外一个非常有用的重定向操作符是 "-",请看下面这个例子:
$ (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xvfp -)
该命令表示把 /source/directory 目录下的所有文件通过压缩和解压,快速的全部移动到 /dest/directory 目录下去,这个命令在 /source/directory 和 /dest/directory 不处在同一个文件系统下时将显示出特别的优势。
下面还几种不常见的用法:
n<&- 表示将 n 号输入关闭
<&- 表示关闭标准输入(键盘)
n>&- 表示将 n 号输出关闭
>&- 表示将标准输出关闭
if...then...else
if 语句用于判断和分支,其语法规则和 C 语言的 if 非常相似。其几种基本结构为:
if [ expression ]
then
statments
fi
或者
if [ expression ]
then
statments
else
statments
fi
或者
if [ expression ]
then
statments
else if [ expression ]
then
statments
else
statments
fi
或者
if [ expression ]
then
statments
elif [ expression ]
then
statments
else
statments
fi
值得说明的是如果你将 if 和 then 简洁的写在一行里面,就必须在 then 前面加上分号,如:if [ expression ]; then下面这个例子说明了如何使用 if 条件判断语句:
#!/bin/bash
if [ $1 -gt 90 ]
then
echo "Good, $1"
elif [ $1 -gt 70 ]
then
echo "OK, $1"
else
echo "Bad, $1"
fi
exit 0
上面例子中的 $1 是指命令行的第一个参数,这个会在后面的“BASH 中的特殊保留字”中讲解。
for
for 循环结构与 C 语言中有所不同,在 BASH 中 for 循环的基本结构是:
for $var in [list]
do
statments
done
其中 $var 是循环控制变量,[list] 是 $var 需要遍历的一个集合,do/done 对包含了循环体,相当于 C 语言中的一对大括号。另外如果do 和 for 被写在同一行,必须在 do 前面加上 ";"。如: for $var in [list]; do 。下面是一个运用 for 进行循环的例子:
#!/bin/bash
for day in Sun Mon Tue Wed Thu Fri Sat
do
echo $day
done
# 如果列表被包含在一对双引号中,则被认为是一个元素
for day in "Sun Mon Tue Wed Thu Fri Sat"
do
echo $day
done
exit 0
注意上面的例子中,在 for 所在那行的变量 day 是没有加 "$" 符号的,而在循环体内,echo 所在行变量 $day 是必须加上 "$" 符号的。另外如果写成 for day 而没有后面的 in [list] 部分,则 day 将取遍命令行的所有参数。如这个程序:
#!/bin/bash
for param
do
echo $param
done
exit 0
上面这个程序将列出所有命令行参数。for 循环结构的循环体被包含在 do/done 对中,这也是后面的 while、until 循环所具有的特点。
while
while 循环的基本结构是:
while [ condition ]
do
statments
done
这个结构请大家自己编写一个例子来验证。
until
until 循环的基本结构是:
until [ condition is TRUE ]
do
statments
done
这个结构也请大家自己编写一个例子来验证。
case
BASH 中的 case 结构与 C 语言中的 switch 语句的功能比较类似,可以用于进行多项分支控制。其基本结构是:
case "$var" in
condition1 )
statments1;;
condition2 )
statments2;;
...
* )
default statments;;
esac
下面这个程序是运用 case 结构进行分支执行的例子:
#!/bin/bash
echo "Hit a key, then hit return."
read Keypress
case "$Keypress" in
[a-z] ) echo "Lowercase letter";;
[A-Z] ) echo "Uppercase letter";;
[0-9] ) echo "Digit";;
* ) echo "Punctuation, whitespace, or other";;
esac
exit 0
上面例子中的第四行 "read Keypress" 一句中的 read 语句表示从键盘上读取输入。这个命令将在本讲义的 BASH 的其他高级问题中讲解。
break/continue
熟悉 C 语言编程的都很熟悉 break 语句和 continue 语句。BASH 中同样有这两条语句,而且作用和用法也和 C 语言中相同,break 语句可以让程序流程从当前循环体中完全跳出,而 continue 语句可以跳过当次循环的剩余部分并直接进入下一次循环。
function my_funcname {
code block
}
或者
my_funcname() {
code block
}
上面的第二种写法更接近于 C 语言中的写法。BASH 中要求函数的定义必须在函数使用之前,这是和 C 语言用头文件说明函数方法的不同。
更进一步的问题是如何给函数传递参数和获得返回值。BASH 中函数参数的定义并不需要在函数定义处就制定,而只需要在函数被调用时用 BASH 的保留变量 $1 $2 ... 来引用就可以了;BASH 的返回值可以用 return 语句来指定返回一个特定的整数,如果没有 return 语句显式的返回一个返回值,则返回值就是该函数最后一条语句执行的结果(一般为 0,如果执行失败返回错误码)。函数的返回值在调用该函数的程序体中通过 $? 保留字来获得。下面我们就来看一个用函数来计算整数平方的例子:
#!/bin/bash
square() {
let "res = $1 * $1"
return $res
}
square $1
result=$?
echo $result
exit 0
ctrl+u 删除光标以前的所有字符
ctrl+d 删除光标以前的一个字符
ctrl+k 删除光标以后的所有字符
ctrl+h 删除光标以后的一个字符
ctrl+t 调换光标前两个字符的次序
ctrl+a 移动光标到最前面
ctrl+e 移动光标到最后面
ctrl+p 上一个命令
ctrl+n 下一个命令
ctrl+s 锁定输入
ctrl+q 解除锁定
ctrl+f 移动光标到后一个字符
ctrl+b 移动光标到前一个字符
ctrl+x 标记一个位置
ctrl+c 清除当前的输入
基本上有两种方法可以执行gawk程序。
如果gawk程序很短,则可以将gawk直接写在命令行,如下所示:
gawk'program'input-file1input-file2...
其中program包括一些pattern和action。
如果gawk程序较长,较为方便的做法是将gawk程序存在一个文件中,gawk的格式如下所示:
gawk-fprogram-fileinput-file1input-file2...
gawk程序的文件不止一个时,执行gawk的格式如下所示:
gawk-fprogram-file1-fprogram-file2...input-file1input-file2...
在gawk中,每一个这样的条目叫做一个记录。它是一个完整的数据的集合。例如,电话号码本中的SmithJohn这个条目,包括他的地址和电话号码,就是一条记录。记录中的每一项叫做一个字段。在gawk中,字段是最基本的单位。多个记录的集合组成了一个文件。大多数情况下,字段之间由一个特殊的字符分开,像空格、TAB、分号等。这些字符叫做字段分隔符。请看下面这个/etc/passwd文件:
tparker;t36s62hsh;501;101;TimParker;/home/tparker;/bin/bash
etreijs;2ys639dj3h;502;101;EdTreijs;/home/etreijs;/bin/tcsh
ychow;1h27sj;503;101;YvonneChow;/home/ychow;/bin/bash
你可以看出/etc/passwd文件使用分号作为字段分隔符。/etc/passwd文件中的每一行都包括七个字段:用户名;口令;用户ID;工作组ID;注释;home目录;启始的外壳。如果你想要查找第六个字段,只需数过五个分号即可。但考虑到以下电话号码本的例子,你就会发现一些问题:
SmithJohn13WilsonSt.555-1283
SmithJohn2736ArtsideDrApt123555-2736
SmithJohn125WestmountCr555-1726
虽然我们能够分辨出每个记录包括四个字段,但gawk却无能为力。电话号码本使用空格作为分隔符,所以gawk认为Smith是第一个字段,John是第二个字段,13是第三个字段,依次类推。就gawk而言,如果用空格作为字段分隔符的话,则第一个记录有六个字段,而第二个记录有八个字段。所以,我们必须找出一个更好的字段分隔符。例如,像下面一样使用斜杠作为字段分隔符:
Smith/John/13WilsonSt./555-1283
Smith/John/2736ArtsideDr/Apt/123/555-2736
Smith/John/125WestmountCr/555-1726
如果你没有指定其他的字符作为字段分隔符,那么gawk将缺省地使用空格或TAB作为字段分隔符。
/pattern1/{action1}
/pattern2/{action2}
/pattern3/{action3}
所有的gawk程序都是由这样的一对对的模式和动作组成的。其中模式或动作都能够被省略,但是两个不能同时被省略。如果模式被省略,则对于作为输入的文件里面的每一行,动作都会被执行。如果动作被省略,则缺省的动作被执行,既显示出所有符合模式的输入行而不做任何的改动。
下面是一个简单的例子,因为gawk程序很短,所以将gawk程序直接写在外壳命令行:
gawk'/tparker/'/etc/passwd
此程序在上面提到的/etc/passwd文件中寻找符合tparker模式的记录并显示(此例中没有动作,所以缺省的动作被执行)。
让我们再看一个例子:
gawk'/UNIX/{print$2}'file2.data
此命令将逐行查找file2.data文件中包含UNIX的记录,并打印这些记录的第二个字段。
你也可以在一个命令中使用多个模式和动作对,例如:
gawk'/scandal/{print$1}/rumor/{print$2}'gossip_file
此命令搜索文件gossip_file中包括scandal的记录,并打印第一个字段。然后再从头搜索gossip_file中包括rumor的记录,并打印第二个字段。
==相等
!=不相等
>大于
<小于
>=大于等于
<=小于等于
例如:gawk'$4>100'testfile将会显示文件testfile中那些第四个字段大于100的记录。
下表列出了gawk中基本的数值运算符。
运算符说明示例
+加法运算2+6
-减法运算6-3
*乘法运算2*5
/除法运算8/4
^乘方运算3^2(=9)
%求余数9%4(=1)
例如:{print$3/2}显示第三个字段被2除的结果。
在gawk中,运算符的优先权和一般的数学运算的优先权一样。例如:{print$1+$2*$3}显示第二个字段和第三个字段相乘,然后和第一个字段相加的结果。你也可以用括号改变优先次序。例如:{print($1+$2)*$3}显示第一个字段和第二个字段相加,然后和第三个字段相乘的结果。
sqrt(x)求x的平方根
sin(x)求x的正弦函数
cos(x)求x的余弦函数
atan2(x,y)求x/y的余切函数
log(x)求x的自然对数
exp(x)求x的e次方
int(x)求x的整数部分
rand()求0和1之间的随机数
srand(x)将x设置为rand()的种子数
index(in,find)在字符串in中寻找字符串find第一次出现的地方,返回值是字符串find出现在字符串in里面的位置。如果在字符串in里面找不到字符串find,则返回值为0。
例如:printindex("peanut","an")
显示结果3。
length(string)求出string有几个字符。
例如:length("abcde")
显示结果5。
match(string,regexp)在字符串string中寻找符合regexp的最长、最靠左边的子字符串。返回值是regexp在string的开始位置,即index值。match函数将会设置系统变量RSTART等于index的值,系统变量RLENGTH等于符合的字符个数。如果不符合,则会设置RSTART为0、RLENGTH为-1。
sprintf(format,expression1,...)和printf类似,但是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)与前面的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。
输入输出的内部函数
close(filename)将输入或输出的文件filename关闭。
system(command)此函数允许用户执行操作系统的指令,执行完毕后将回到gawk程序。
例如:BEGIN{system("ls")}
我们可以让动作显示一些比较复杂的结果。例如:
gawk'$1!="Tim"{print$1,$5,$6,$2}'testfile
你也可以使用一些换码控制符格式化整行的输出。之所以叫做换码控制符,是因为gawk对这些符号有特殊的解释。下面列出常用的换码控制符:
\a警告或响铃字符。
\b后退一格。
\f换页。
\n换行。
\r回车。
\tTab。
\v垂直的tab。
在gawk中,缺省的字段分隔符一般是空格符或TAB。但你可以在命令行使用-F选项改变字符分隔符,只需在-F后面跟着你想用的分隔符即可。
gawk-F";"'/tparker/{print}'/etc/passwd
在此例中,你将字符分隔符设置成分号。注意:-F必须是大写的,而且必须在第一个引号之前。
再例如,你希望既和cat又和CAT匹配,则可以使用或(|):
/cat|CAT/{print}
在gawk中,有几个字符有特殊意义。下面列出可以用在gawk格式中的这些字符:
?^表示字段的开始。
例如:
$3~/^b/
如果第三个字段以字符b开始,则匹配。
?$表示字段的结束。
例如:
$3~/b$/
如果第三个字段以字符b结束,则匹配。
?.表示和任何单字符m匹配。
例如:
$3~/i.m/
如果第三个字段有字符i,则匹配。
?|表示“或”。
例如:
/cat|CAT/
和cat或CAT字符匹配。
?*表示字符的零到多次重复。
例如:
/UNI*X/
和UNX、UNIX、UNIIX、UNIIIX等匹配。
?+表示字符的一次到多次重复。
例如:
/UNI+X/
和UNIX、UNIIX等匹配。
?\{a,b\}表示字符a次到b次之间的重复。
例如:
/UNI\{1,3\}X
和UNIX、UNIIX和UNIIIX匹配。
??表示字符零次和一次的重复。
例如:
/UNI?X/
和UNX和UNIX匹配。
?[]表示字符的范围。
例如:
/I[BDG]M/
和IBM、IDM和IGM匹配
?[^]表示不在[]中的字符。
例如:
/I[^DE]M/
和所有的以I开始、M结束的包括三个字符的字符串匹配,除了IDM和IEM之外。
你可以使用如下命令调用gawk程序:
gawk-fscriptfilename
此命令使gawk对文件filename执行名为script的gawk程序。
如果你不希望使用缺省的字段分隔符,你可以在f选项后面跟着F选项指定新的字段分隔符(当然你也可以在gawk程序中指定),例如,使用分号作为字段分隔符:
gawk-fscript-F";"filename
如果希望gawk程序处理多个文件,则把各个文件名罗列其后:
gawk-fscriptfilename1filename2filename3...
缺省情况下,gawk的输出将送往屏幕。但你可以使用Linux的重定向命令使gawk的输出送往一个文件:
gawk-fscriptfilename>save_file
BEGIN和END中所有要执行的指令都应该用花括号括起来。BEGIN和END必须使用大写。
请看下面的例子:
BEGIN{print"Startingtheprocessthefile"}
$1=="UNIX"{print}
$2>10{printf"Thislinehasavalueof%d",$2}
END{print"Finishedprocessingthefile.Bye!"}
此程序中,先显示一条信息:Startingtheprocessthefile,然后将所有第一个字段等于UNIX的整条记录显示出来,然后再显示第二个字段大于10的记录,最后显示信息:Finished processingthefile.Bye!。
请看下面的例子:
$1=="Plastic"{count=count+1}
如果第一个字段是Plastic,则count的值加1。在此之前,我们应当给count赋予过初值,一般是在BEGIN部分。
下面是比较完整的例子:
BEGIN{count=0}
$5=="UNIX"{count=count+1}
END{printf"%doccurrencesofUNIXwerefound",count}
变量可以和字段和数值一起使用,所以,下面的表达式均为合法:
count=count+$6
count=$5-8
count=$5+var1
变量也可以是格式的一部分,例如:
$2>max_value{print"Maxvalueexceededby",$2-max_value}
$4-var1<min_value{print"Illegalvalueof",$4}
gawk语言中有几个十分有用的内置变量,现在列于下面:
NR已经读取过的记录数。
FNR从当前文件中读出的记录数。
FILENAME输入文件的名字。
FS字段分隔符(缺省为空格)。
RS记录分隔符(缺省为换行)。
OFMT数字的输出格式(缺省为%g)。
OFS输出字段分隔符。
ORS输出记录分隔符。
NF当前记录中的字段数。
如果你只处理一个文件,则NR和FNR的值是一样的。但如果是多个文件,NR是对所有的文件来说的,而FNR则只是针对当前文件而言。
例如:
NR<=5{print"Notenoughfieldsintherecord"}
检查记录数是否小于5,如果小于5,则显示出错信息。
FS十分有用,因为FS控制输入文件的字段分隔符。例如,在BEGIN格式中,使用如下的命令:
FS=":"
while循环
while循环的语法如下:
while(expression){
commands
}
例如:
#interest calculation computes compound interest
#inputs from a file arethea mount,interest_rateandyears
{var=1
while(var<=$3){
printf("%f\n",$1*(1+$2)^var)
var++
for循环
for循环的语法如下:
for(initialization;expression;increment){
command
}
例如:
#interest calculation computes compound interest
#inputs from a fil earethea mount,interest_rateandyears
{for(var=1;var<=$3;var++){
printf("%f\n",$1*(1+$2)^var)
}}
next和exit
next指令用来告诉gawk处理文件中的下一个记录,而不管现在正在做什么。语法如下:
{command1
command2
command3
next
command4
}
程序只要执行到next指令,就跳到下一个记录从头执行命令。因此,本例中,command4指令永远不会被执行。
程序遇到exit指令后,就转到程序的末尾去执行END,如果有END的话。
arrayname[num]=value
请看下面的例子:
#reverse lines in a file
{line[NR]=$0} #remember each line
END{var=NR #output lines in reverse order
while(var>0){
printline[var]
var--
}
此段程序读取一个文件的每一行,并用相反的顺序显示出来。我们使用NR作为数组的下标来存储文件的每一条记录,然后在从最后一条记录开始,将文件逐条地显示出来。
复杂的gawk程序常常可以使用自己定义的函数来简化。调用用户自定义函数与调用内部函数的方法一样。函数的定义可以放在gawk程序的任何地方。
用户自定义函数的格式如下:
functionname(parameter-list){
body-of-function
}
name是所定义的函数的名称。一个正确的函数名称可包括一序列的字母、数字、下标线(underscores),但是不可用数字做开头。parameter-list是函数的全部参数的列表,各个参数之间以逗点隔开。body-of-function包含gawk的表达式,它是函数定义里最重要的部分,它决定函数实际要做的事情。
下面这个例子,会将每个记录的第一个字段的值的平方与第二个字段的值的平方加起来。
{print"sum=",SquareSum($1,$2)}
function SquareSum(x,y){
sum=x*x+y*y
returnsum
}
gawk'{if(NF>max)max=NF}
END{printmax}'
此程序会显示所有输入行之中字段的最大个数。
gawk'length($0)>80'
此程序会显示出超过80个字符的每一行。此处只有模式被列出,动作是采用缺省值显示整个记录。
gawk'NF>0'
显示拥有至少一个字段的所有行。这是一个简单的方法,将一个文件里的所有空白行删除。
gawk'BEGIN{for(i=1;i<=7;i++)
printint(101*rand())}'
此程序会显示出范围是0到100之间的7个随机数。
ls-lfiles|gawk'{x+=$4};END{print"totalbytes:"x}'
此程序会显示出所有指定的文件的总字节数。
expandfile|gawk'{if(x<length())x=length()}
END{print"maximumlinelengthis"x}'
此程序会将指定文件里最长一行的长度显示出来。expand会将tab改成space,所以是用实际的右边界来做长度的比较。
gawk'BEGIN{FS=":"}
{print$1|"sort"}'/etc/passwd
此程序会将所有用户的登录名称,依照字母的顺序显示出来。
gawk'{nlines++}
END{printnlines}'
此程序会将一个文件的总行数显示出来。
gawk'END{printNR}'
此程序也会将一个文件的总行数显示出来,但是计算行数的工作由gawk来做。
gawk'{printNR,$0}'
此程序显示出文件的内容时,会在每行的最前面显示出行号,它的函数与‘cat-n’类似。
“别期望在一刻钟内就能领略Perl的所有神奇之处,这种情况很像吃香蕉,用不着吃完整只香蕉后才知其味,每咬一口都是享受,并促使你再咬下一口,再下一口。”上面这段话是Perl项目发起人劳利·华尔(LarryWall)对学习Perl语言的一段经典评论,希望大家都能找到这种感觉。
Perl的设计目标是帮助UNIX用户完成一些常见的任务,这些任务对于Shell来说过于沉重或对移植性要求过于严格。Perl语言中包含了C、C++、shell,script、sed、awk这几个语言的语法,它最初的目的就是用来取代UNIX中sed/awk与脚本语言的组合,用来汇整信息,产生报表。因此Perl语言要远远比前面讲的BASH复杂和功能强大。Perl的设计原则或者说Perl的设计哲学是以实用为第一优先,也就是力图使Perl语言容易使用、有效率、而且完整。
Perl是按GNUPublicLicense和ArticticLicense两种许可证形式分发的,其实质是开源软件、自由软件的,原先运行于UNIX和类UNIX系统,现在已可以方便地在OS/2,Windows9x,Windows/NT等系统下运行。Perl是一种解释运行的语言,和BASH程序一样,一般Perl程序的第一行需注明自己是一个Perl程序而不是Shell程序,所以一般将下面一行语句:
#!/usr/bin/perl 作为文件的第一行。
Perl由于引入了模块的设计思想,随着版本的改进,功能越来越强。现在Perl的功能已经超乎原先设计时的想象,几乎任何事都可以做到,也变成每一部工作站必备的标准工具了。Perl最为著名的一点就是他对字符串的处理,由于Internet对文字信息处理的巨大需求,使得Perl的应用如日中天,而且Perl语言也的确是一个非常优秀的文字信息处理语言。
一个有用的Perl程序可以很短。例如希望更换大量文件中的一些相同内容,可以使用下面的一条命令:
perl-e's/gopher/WorldWideWeb/gi'-p-i.bak*.html
下面是一个基本的perl程序:
#!/usr/local/bin/perl
#
#Programtodotheobvious
print'Helloworld.';#只是简单地显示出Helloworld.字符串。
Perl中最基本的变量类型是标量。标量既可以是数字,也可以是字符串,而且两者是可以互换的。具体是数字还是字符串,可以有上下文决定。标量变量的语法为$variable_name。例如:
$priority=9;
把9赋予标量变量$priority,你也可以将字符串赋予该变量:
$priority='high';
注意在Perl中,变量名的大小写是敏感的,所以$a和$A是不同的变量。
以下的数值或字符串都可以赋给标量:
12312.45E-100xff(hex)0377(octal)
'Whatyou$seeis(almost)what\nyouget''Don\'tWalk'
"Howareyou?""Substitutevaluesof$xand\nin\"quotes."
`date``uptime-u``du-sk$filespec|sort-n`
$x$list_of_things[5]$lookup{'key'}
从上面可以看出,Perl中有三种类型的引用。双引号("")括起来的字符串中的任何标量和特殊意义的字符都将被Perl解释。如果不想让Perl解释字符串中的任何标量和特殊意义的字符,应该将字符串用单括号括起来。这时,Perl不解释其中的任何字符,除了\\和\'。最后,可以用(`)将命令括起来,这样,其中的命令可以正常运行,并能得到命令的返回值。请看下面的例子:
1#!/usr/bin/perl
2$folks="100";
3print"\$folks=$folks\n";
4print'\$folks=$folks\n';
5print"\n\nBEEP!\a\LSOMEBLANK\ELINESHERE\n\n";
6$date=`date+%D`;
7print"Todayis[$date]\n";
8chop$date;
9print"Dateafterchoppingoffcarriagereturn:[".$date."]\n";
注意实际程序中不应该包括行号。其输出结果如下:
$folks=100
$folks=$folks\n
BEEP!someblankLINESHERE
Todayis[03/29/96]
Dateafterchoppingoffcarriagereturn:[03/29/96]
第3行显示$folks的值。$之前必须使用换码符\,以便Perl显示字符串$folks而不是$folks的值100。
第4行使用的是单引号,结果Perl不解释其中的任何内容,只是原封不动地将字符串显示出来。
第6行使用的是(`),则date+%D命令的执行结果存储在标量$date中。
上例中使用了一些有特殊意义的字符,下面列出这些字符的含义:
\n换行。
\r回车。
\t制表符。
\a蜂鸣声。
\bBackspace。
\L\E将\L和\E之间的字符转换成小写。
\l将其后的字符转换成小写。
\U\E将\U和\E之间的字符转换成大写。
\u将其后的字符转换成大写。
\cC插入控制字符C。
\x##十六进制数##。
\0ooo八进制数ooo。
\\反斜杠。
\按原样输出下一个字符,例如:\$输出$。
Perl中的数字是以浮点形式存储的。下面列出有关的数字运算符:
$a=1+2;#1加2,结果存储在$a中。
$a=3-4;#3减4,结果存储在$a中。
$a=5*6;#5乘6,结果存储在$a中。
$a=7/8;#7除以8,结果存储在$a中。
$a=9**10;#9的10次方,结果存储在$a中。
$a=5%2;#取5除2的余数,结果存储在$a中。
++$a;#$a加1,然后赋予$a。
$a++;#先将$a返回,然后$a加1。
--$a;#$a减1,然后赋予$a。
$a--;#先将$a返回,然后$a减1。
Perl支持的逻辑运算符:
$r=$x||$y$r=$x或$y。
$r=$x&&$y$r=$x与$y。
$r=!$x$r=非$x。
对于字符标量,Perl支持下面的运算符:
$a=$b.$c;#将$b和$c连接,然后赋予$a。
$a=$bx$c;#$b重复$c次,然后赋予$a。
下面是Perl中的赋值方法:
$a=$b;#将$b赋予$a。
$a+=$b;#$b加$a,然后赋予$a。
$$a-=$b;#$a减$b,然后赋予$a。
$a.=$b;#把$b连接到$a的后面,然后赋予$a。
你也可以使用下面的比较运算符:
$x==$y如果$x和$y相等,则返回真。
$x!=$y如果$x和$y不相等,则返回真。
$x<$y如果$x比$y小,则返回真。
$x<=$y如果$x小于等于$y,则返回真。
$x>$y如果$x比$y大,则返回真。
$x>=$y如果$x大于等于$y,则返回真。
$xeq$y如果字符串$x和字符串$y相同,则返回真。
数组
数组也叫做列表,是由一系列的标量组成的。数组变量以@开头。请看以下的赋值语句:
@food=("apples","pears","eels");
@music=("whistle","flute");
数组的下标从0开始,你可以使用方括号引用数组的下标:
$food[2]
返回eels。注意@已经变成了$,因为eels是一个标量。
在Perl中,数组有多种赋值方法,例如:
@moremusic=("organ",@music,"harp");
@moremusic=("organ","whistle","flute","harp");
还有一种方法可以将新的元素增加到数组中:
push(@food,"eggs");
把eggs增加到数组@food的末端。要往数组中增加多个元素,可以使用下面的语句:
push(@food,"eggs","lard");
push(@food,("eggs","lard"));
push(@food,@morefood);
push返回数组的新长度。
pop用来将数组的最后一个元素删除,并返回该元素。例如:
@food=("apples","pears","eels");
$grub=pop(@food);#此时$grub="eels"
请看下面的例子:
1#!/usr/bin/perl
2#
3#AnexampletoshowhowarraysworkinPerl
4#
5@amounts=(10,24,39);
6@parts=('computer','rat',"kbd");
7
8$a=1;$b=2;$c='3';
9@count=($a,$b,$c);
10
11@empty=();
12
13@spare=@parts;
14
15print'@amounts=';
16print"@amounts\n";
17
18print'@parts=';
19print"@parts\n";
20
21print'@count=';
22print"@count\n";
23
24print'@empty=';
25print"@empty\n";
26
27print'@spare=';
28print"@spare\n";
29
30
31#
32#Accessingindividualitemsinanarray
33#
34print'$amounts[0]=';
35print"$amounts[0]\n";
36print'$amounts[1]=';
37print"$amounts[1]\n";
38print'$amounts[2]=';
39print"$amounts[2]\n";
40print'$amounts[3]=';
41print"$amounts[3]\n";
42
43print"Itemsin\@amounts=$#amounts\n";
44$size=@amounts;print"SizeofAmount=$size\n";
45print"Item0in\@amounts=$amounts[$[]\n";
以下是显示结果:
@amounts=102439
@parts=computerratkbd
@count=123
@empty=
@spare=computerratkbd
$amounts[0]=10
$amounts[1]=24
$amounts[2]=39
$amounts[3]=
Itemsin@amounts=2
SizeofAmount=3
Item
第5行,三个整数值赋给了数组@amounts。第6行,三个字符串赋给了数组@parts。第8行,字符串和数字分别赋给了三个变量,然后将三个变量赋给了数组@count。11行创建了一个空数组。13行将数组@spare赋给了数组@parts。15到28行输出了显示的前5行。34到41行分别存取数组@amounts的每个元素。注意$amount[3]不存在,所以显示一个空项。43行中使用$#array方式显示一个数组的最后一个下标,所以数组@amounts的大小是($#amounts+1)。44行中将一个数组赋给了一个标量,则将数组的大小赋给了标量。45行使用了一个Perl中的特殊变量$[,用来表示一个数组的起始位置(缺省为0)。
相关数组
一般的数组允许我们通过数字下标存取其中的元素。例如数组@food的第一个元素是$food[0],第二个元素是$food[1],以此类推。但Perl允许创建相关数组,这样我们可以通过字符串存取数组。其实,一个相关数组中每个下标索引对应两个条目,第一个条目叫做关键字,第二个条目叫做数值。这样,你就可以通过关键字来读取数值。相关数组名以百分号(%)开头,通过花括号({})引用条目。例如:
%ages=("MichaelCaine",39,
"DirtyDen",34,
"Angie",27,
"Willy","21indogyears",
"TheQueenMother",108);
这样我们可以通过下面的方法读取数组的值:
$ages{"MichaelCaine"};#Returns39
$ages{"DirtyDen"};#Returns34
$ages{"Angie"};#Returns27
$ages{"Willy"};#Returns"21indogyears"
$ages{"TheQueenMother"};#Returns108
open函数打开一个文件以供读取,其中第一个参数是文件句柄(filehandle)。文件句柄用来供Perl在以后指向该文件。第二个参数指向该文件的文件名。close函数关闭该文件。
open函数还可以用来打开文件以供写入和追加,只须分别在文件名之前加上>和>>:
open(INFO,$file);#Openforinput
open(INFO,">$file");#Openforoutput
open(INFO,">>$file");#Openforappending
open(INFO,"<$file");#Alsoopenforinput
另外,如果你希望输出内容到一个已经打开供写入的文件中,你可以使用带有额外参数的print语句。例如:
printINFO"Thislinegoestothefile.\n";
最后,可以使用如下的语句打开标准输入(通常为键盘)和标准输出(通常为显示器):
open(INFO,'-');#Openstandardinput
open(INFO,'>-');#Openstandardoutput
一个Perl程序在它一启动时就已经有了三个文件句柄:STDIN(标准输入设备),STDOUT(标准输出设备)和STDERR(标准错误信息输出设备)。如果想要从一个已经打开的文件句柄中读取信息,可以使用<>运算符。
使用read和write函数可以读写一个二进制的文件。其用法如下:
read(HANDLE,$buffer,$length[,$offset]);
此命令可以把文件句柄是HANDLE的文件从文件开始位移$offset处,共$length字节,读到$buffer中。其中$offset是可选项,如果省略$offset,则read()从当前位置的前$length个字节读取到当前位置。可以使用下面的命令查看是否到了文件末尾:
eof(HANDLE);
如果返回一个非零值,则说明已经到达文件的末尾。
打开文件时可能出错,所以可以使用die()显示出错信息。下面打开一个叫做“test.data”的文件:
open(TESTFILE,"test.data")||die"\n$0Cannotopen$!\n";
foreach$morsel(@food)#Visiteachiteminturn
#andcallit$morsel
{
print"$morsel\n";#Printtheitem
print"Yumyum\n";#Thatwasnice
}
每次要执行的命令用花括号括出。第一次执行时$morsel被赋予数组@food的第一个元素的值,第二次执行时$morsel被赋予数组@food的第二个元素的值,以此类推直到数组的最后一个元素。
判断运算
在Perl中任何非零的数字和非空的字符串都被认为是真。零、全为零的字符串和空字符串都为假。
下面是一些判断运算符:
$a==$b如果$a和$b相等,则返回真。
$a!=$b如果$a和$b不相等,则返回真。
$aeq$b如果字符串$a和字符串$b相同,则返回真
$ane$b如果字符串$a和字符串$b不相同,则返回真。
你可以使用逻辑运算符:
($a&&$b)$a与$b。
($a||$b)$a或$b。
!($a)非$a。
for循环
Perl中的for结构和C语言中的for结构基本一样:
for(initialise;test;inc){
first_action;
second_action;
etc
}
下面是一个for循环的例子,用来显示从0到9的数字:
for($i=0;$i<10;++$i)#Startwith$i=1
#Doitwhile$i<10
#Increment$ibeforerepeating
{
print"$i\n";
}
while和until循环
下面是一个while和until循环的例子。它从键盘读取输入直到得到正确的口令为止。
#!/usr/local/bin/perl
print"Password?";#Askforinput
$a=<STDIN>;#Getinput
chop$a;#Removethenewlineatend
while($ane"fred")#Whileinputiswrong...
{
print"sorry.Again?";#Askagain
$a=<STDIN>;#Getinputagain
chop$a;#Chopoffnewlineagain
}
当输入和口令不相等时,执行while循环。
你也可以在执行体的末尾处使用while和until,这时需要用do语句:
#!/usr/local/bin/perl
do
{
"Password?";#Askforinput
$a=<STDIN>;#Getinput
chop$a;#Chopoffnewline
}
while($ane"fred")#Redowhilewronginput
条件结构
Perl也允许if/then/else表达式。请看下面的例子:
if($a){
print"Thestringisnotempty\n";
}
else{
print"Thestringisempty\n";
}
注意在Perl中,空字符被认为是假。
If结构中也可以使用嵌套结构:
if(!$a)#The!isthenotoperator
{
print"Thestringisempty\n";
}
elsif(length($a)==1)#Ifabovefails,trythis
{
print"Thestringhasonecharacter\n";
}
elsif(length($a)==2)#Ifthatfails,trythis
{
print"Thestringhastwocharacters\n";
}
else#Now,everythinghasfailed
{
print"Thestringhaslotsofcharacters\n";
下面列出了RE中许多具有特殊意义的字符:
.任何字符除了换行符(\n)
^一行和一个字符串的开始
$一行和一个字符串的结束
*其前一个字符重复零次或多次
+其前一个字符重复一次或多次
?其前一个字符重复零次或一次
例如:
if($x=~/l.mp/){print"YES"}
对于$x=“lamp”、“lump”、“slumped”将显示YES,但对于$x=“lmp”或“lessamperes”将不会显示YES。
再看下面的例子:
/fr.*nd/匹配frnd、friend、frontandback。
/fr.+nd/匹配frond、friend、frontandback。但不匹配frnd。
/10*1/匹配11、101、1001、100000001。
/b(an)*a/匹配ba、bana、banana、banananana。
/flo?at/匹配flat和float,但不匹配flooat。
方括号用来匹配其中的任何字符。方括号中的-符号用来表明在什么之间,^符号表明非的意思。
[0123456789]匹配任何单个的数字。
[0-9]匹配任何单个的数字。
[a-z]+匹配任何由小写字母组成的单词。
[^0-9]匹配任何非数字的字符。
反斜杠还是用于转义。如果你想匹配+、?、.、*、^、$、(、)、[、]、{、}、|、\和/这些字符,则其前面必须用反斜杠(\)。例如:
/10.2/匹配10Q2、1052和10.2。
/10\.2/匹配10.2,但不和10Q2或1052匹配。
/\*+/匹配一个或多个星号。
/A:\\DIR/匹配A:\DIR。
/\/usr\/bin/匹配/usr/bin。
下面还有一些特殊意义的字符:
\n换行。
\t制表符。
\w任何字母和数字和[a-zA-Z0-9_]一样。
\W任何非字母和数字和[^a-zA-Z0-9_]一样。
\d任何数字和[0-9]一样。
\D任何非数字和[^0-9]一样。
\s任何空白字符:空格、tab、换行等。
\S任何非空白字符。
\b单词边界,只对[]以外有效。
\B非单词边界。
替换
Perl可以使用s函数利用字符匹配的结果进行字符替换。s函数和vi编辑器的作用基本一样。这时还是使用字符匹配运算符=~,例如:将字符串$sentence中所出现的london用London替换,可以使用如下的命令:
$sentence=~s/london/London/
命令的返回值是所做的替换数目。
但此命令只能替换第一个出现的london。如果希望将所有在字符串中出现的london都用London替换,则应使用/g选项:
s/london/London/g
此命令的对象是$_变量,也就是当前的缺省变量。
如果希望还能替换类似lOndon、lonDON、LoNDoN的字符串,可以使用:
s/[Ll][Oo][Nn][Dd][Oo][Nn]/London/g
但更为简单的方法是使用i选项,也就是忽略大小写:
s/london/London/gi
翻译
tr函数允许逐字地翻译。下面的命令使得字符串$sentence中的a、b、c分别由e、f、d代替:
$sentence=~tr/abc/efd/
结果返回所做的替换数目。
大多数RE中的特殊字符在tr函数中并不存在。例如下面的命令用来计算字符串$sentence中星号(*)的数目,并将结果存储在$count:
$count=($sentence=~tr/*/*/);
参数
调用一个子过程时,所有的参数都传送到了数组@_中。下面子过程的例子显示出所有的
参数:
subprintargs{
print"@_\n";
}
&printargs("perly","king");#Exampleprints"perlyking"
&printargs("frog","and","toad");#Prints"frogandtoad"
返回值
下面的例子返回两个输入参数的最大值:
submaximum{
if($_[0]>$_[1]){
$_[0];
}
else{
$_[1];
$biggest=&maximum(37,24);#Now$biggestis37
此程序从一个记录学生信息的文件stufile和一个记录学生成绩的文件scorefile中生成一个
学生成绩报告单。
输入文件stufile由学生ID、姓名和年级三个字段组成,其间由分号隔开:
123456;Washington,George;SR
246802;Lincoln,Abraham"Abe";SO
357913;Jefferson,Thomas;JR
212121;Roosevelt,Theodore"Teddy";SO
文件scorefile由学生ID、科目号、分数三个字段组成,由空格隔开:
123456 1 98
212121 1 86
246802 1 89
357913 1 90
123456 2 96
212121 2 88
357913 2 92
123456 3 97
212121 3 96
246802 3 95
357913 3 94
程序应该输出如下的结果:
Stu-IDName...123Totals:
357913Jefferson,Thomas909294276
246802Lincoln,Abraham"Abe"8995184
212121Roosevelt,Theodore"Teddy"868896270
123456Washington,George989697291
Totals:363276382
源程序如下:
#!/usr/local/bin/perl
#Gradebook-demonstratesI/O,associative
#arrays,sorting,andreportformatting.
#Thisaccommodatesanynumberofexamsandstudents
#andmissingdata.Inputfilesare:
$stufile='stufile';
$scorefile='scorefile';
#Iffileopenssuccessfully,thisevaluatesas"true",andPerl
#doesnotevaluaterestofthe"or""||"
open(NAMES,"<$stufile")||die"Can'topen$stufile$!";
open(SCORES,"<$scorefile")||die"Can'topen$scorefile$!";
#Buildanassociativearrayofstudentinfo
#keyedbystudentnumber
while(<NAMES>){
($stuid,$name,$year)=split(':',$_);
$name{$stuid}=$name;
if(length($name)>$maxnamelength){
$maxnamelength=length($name);
}}
closeNAMES;
#Buildatablefromthetestscores:
while(<SCORES>){
($stuid,$examno,$score)=split;
$score{$stuid,$examno}=$score;
if($examno>$maxexamno){
$maxexamno=$examno;
}}
closeSCORES;
#Printthereportfromaccumulateddata!
printf"%6s%-${maxnamelength}s",
'Stu-ID','Name...';
foreach$examno(1..$maxexamno){
printf"%4d",$examno;
}
printf"%10s\n\n",'Totals:';
#Subroutine"byname"isusedtosortthe%namearray.
#The"sort"functiongivesvariables$aand$bto
#subroutinesitcalls.
#"xcmpy"functionreturns-1ifx<y,0ifx=y,
#+1ifx>y.SeethePerldocumentationfordetails.
subbyname{$name{$a}cmp$name{$b}}
#OrderstudentIDssothenamesappearalphabetically:
foreach$stuid(sortbynamekeys(%name)){
#Printscoresforastudent,andatotal:
printf"%6d%-${maxnamelength}s",
$stuid,$name{$stuid};
$total=0;
foreach$examno(1..$maxexamno){
printf"%4s",$score{$stuid,$examno};
$total+=$score{$stuid,$examno};
$examtot{$examno}+=$score{$stuid,$examno};
}
printf"%10d\n",$total;
}
printf"\n%6s%${maxnamelength}s","Totals:";
foreach$examno(1..$maxexamno){
printf"%4d",$examtot{$examno};
}
print"\n";
exit(0);
正则表达式在 shell、工具程序、Perl 语言中有非常重要的地位。正则表达式通过一些特殊符号表示特定的字符串模式。常见的特殊字符包括:
字符 功能
^ 置于待搜索的字符串之前,匹配行首的字
$ 置于待搜索的字符串之后,匹配行末的字
\< 匹配一个字的字头
\> 匹配一个字的字尾
. 匹配任意单个正文字符
[str] 匹配字符串 str 中的任意单个字符
[^str] 匹配不在字符串 str 中的任意单个字符
[a-c] 匹配从 a 到 c 之间的任一字符
* 匹配前一个字符的 0 次或多次出现
\ 忽略特殊字符的特殊含义,将其看作普通字符
扩充的特殊字符:
字符 功能
+ 重复匹配前一项 1 次以上
? 重复匹配前一项 0 次或 1 次
{j} 重复匹配前一项 j 次
{j, } 重复匹配前一项 j 次以上
{, k} 重复匹配前一项最多 k 次
{j, k} 重复匹配前一项 j 到 k 次
s | t 匹配 s 或 t 中的一项
(exp) 将表达式 exp 作为单项处理