Perl5学习笔记-十五章-附录
附录一
编程标准与错误处理
函数集(未定稿)
一、进程处理函数
1、进程启动函数
2、进程终止函数
3、进程控制函数
4、其它控制函数
二、数学函数
三、字符串处理函数
四、标量转换函数
五、数组和列表函数
六、关联数组函数
编程标准
每个程序员都有他自己的喜好格式化的方法,但也有一些通用的准则,会让你的程序更容易阅读,理解和维护。最重要的是-w标志在任何时间下运行程序。你可以把它明确地没有任何警告杂注或$^W变量通过特定部分的代码,如果你一定要。你也应该总是运行在使用严格的或知道的原因下,为什么不呢。使用sigtrap ,甚至使用诊断程序的编译指示也可能是有用的。
关于美学的代码布局,的唯一强烈的Larry关心的是,多行块的右大括号排队的关键字开始构建。 除此之外,他还有其他的喜好是没有那么强的:
4空格缩进。
如果可能的话,左大作为关键字在同一行上,否则按顺序。
空格句前花多行块。
一个行块可以被放在同一行,包括花括号。
不要有空格在分号前。
在"short"一行BLOCK分号省略。
大多数操作符两边要用空格.
一个"复杂的"下标(括号内)两边要用空格.
B块之间空行,做不同的事情。
不要空挂else。
函数名称和左括号之间不要有空格 。
每个逗号后要有空格。
长的行折断后操作员(除了和或(or,and))。
当前行的最后一个括号匹配后空格。
垂直行相应的项(条)目。
只要清晰就不会有麻烦省略多余的标点符号。
这里有一些其他风格更实质性的问题需要考虑:
仅仅因为你能做一些事情并不意味着我应该以一种特殊的方式做。Perl是设计给你几种方法可以做任何事情,所以考虑选择最可读的。例如:
open(FOO,$foo) || die "Can't open $foo: $!";
是比以下要的好:
die "Can't open $foo: $!" unless open(FOO,$foo);
因为第二种方式隐藏的修饰符声明的主要观点。另一方面
print "Starting analysis\n" if $verbose;
明显优于
$verbose && print "Starting analysis\n";
因为主要的问题不在于用户输入的-v或不是。不要去愚蠢的扭曲退出循环的顶部或底部,当Perl提供了最后的运算符,这样你就可以在中间退出。"减少缩进"这一点,使之更可见:
LINE:
for (;;) {
statements;
last LINE if $foo;
next LINE if /^#/;
statements;
}
不要害怕使用循环标记 - 它们以提高可读性以及允许多级循环中断。见前面的例子。
在真空环境中,避免使用的grep()(或map())或`反引号',也就是说,当你抛弃它们的返回值,所有这些功能有返回值使用它们。否则使用foreach()循环或system()函数代替。
对于便携性,使用时无法在每一台机器上实现的功能,测试在一个eval的构建体,看它是否失败。如果你知道是什么版本或最接近的一个特定的功能已实现,可以测试 $] ($Perl_VERSION)去看它是否存在。配置模块还可以让你询问的配置程序安装Perl的时确定的值。
选择助记符标识符。如果你不记得助记符意味着什么,你已经有了一个问题。
虽然短标识符如$gotit,或许没问题,用下划线来分隔单词的较长的标识符。这是通常$var_names_like_this比$VarNamesLikeThis比较容易理解,尤其是对于非英语母语的人。这也是一个简单的规则作用和VAR_NAMES_LIKE_THIS一致。
有时,包名是这个规则的一个例外。Perl非正式保留小写的模块名称为"杂"模块,如整数及严格。其他模块应以大写字母开始,并使用大小写混合,但可能没有强调由于限制在原始文件系统的模块名作为文件必须符合一些稀疏的字节表示的。
如果你有一个真正的复杂正则表达式,使用/x修饰符,并在一些空白,使其看上去不像行噪声。 不要使用斜杠作为分隔符,当你的正则表达式有斜杠或反斜杠。
请务必检查系统调用的返回代码。好的错误消息应该到STDERR,包括哪些程序引起的问题,什么是失败的系统调用和参数,(非常重要)标准的系统错误消息应包含什么地方出了错。这里有一个简单但足够的例子:
opendir(D, $dir) or die "can't opendir $dir: $!";
想想可重用性。为什么要浪费脑力上的一个一次性时,你可能想再次做类似的东西吗?概括你的代码。考虑写一个模块或对象类。考虑使你的代码干净,使用严格的警告和使用(或-w)有效运行。考虑放弃你的代码。考虑改变你的整个世界观。试想一下......哦,没关系。
错误处理
warn函数
警告函数只是提出了一个警告,一个消息被打印到STDERR,但没有采取进一步的行动。
chdir('/etc') or warn "Can't change directory";
die函数
die函数的工作原理就像警告,但它也调用exit。在一个正常的脚本,这个函数有立即终止执行的效果。
chdir('/etc') or die "Can't change directory";
模块中的错误报告
有两种不同的情况,我们需要能够处理:
报告错误的模块中引用的模块的文件名和行号 - 这是非常有用的调试模块时,或者当您特别希望提出一个模块相关的,而不是脚本相关的,错误的。
在一个模块中,引用了调用者的信息,以便可以调试行内的脚本导致错误的错误报告。在这种方式中引发的错误最终用户是非常有用的,因为它们在调用脚本的起源突出显示错误。
警告和die函数正常工作方式略有不同,比你期望从一个模块内调用。例如,简单的模块:
package T;
require Exporter;
@ISA = qw/Exporter/;
@EXPORT = qw/function/;
use Carp;
# by www.gitbook.net
sub function{
warn "Error in module!";
}
1;
从脚本调用时,
use T;
function();
It will produce following result
Error in module! at T.pm line 11.
这个是多还是少,你所期望的,但不一定是你想要的。从一个模块程序员的角度来看,信息是有用的,因为它有助于以指向一个错误所在模块的本身。为最终用户提供的信息是相当无用的,但硬化的程序员,它完全没有意义的。
对于这样的问题的解决方案是的Carp 模块,它提供了一个简单的方法报告错误返回调用脚本的信息模块内。Carp模块提供了四个函数:carp, cluck, croak, confess. 这些功能将在下面讨论。
Carp函数
carp 函数是基本等效的警告,并没有真正退出脚本和打印脚本的名称打印到STDERR的信息。
carp "Error in module!";
#by www.gitbook.net
This would result in
Error in module! at test.pl line 3
Cluck函数
cluck 函数是一种增压carp, 它遵循相同的基本原则,而且打印的所有模块,导致被调用的函数的堆栈跟踪,包括信息的原始脚本。
cluck "Error in module!";
This would result in something like
Error in module! at T.pm line 11
T::function() called at S.pm line 13
S::raise() called at test.pl line 3
Croak函数
croak 函数是相当于die,但它报告的调用上一级。就像die,此功能也可以退出脚本报告错误STDERR:
croak "Definitely didn't work";
This would result in
Error in module! at S.pm line 13
和carp相同的基本规则适用于包括行和文件信息的警告和死亡的函数。
Confess函数
confess 功能是一样cluck,它调用die,然后打印一个堆栈跟踪所有的方式的产生脚本。
confess "Failed around about there";
This would result in
Error in module! at T.pm line 11
T::function() called at S.pm line 13
S::raise() called at t2.pl line 3
一、进程处理函数
1、进程启动函数
函数名:eval
调用语法:eval(string)
解说:将string看作Perl语句执行。
正确执行后,系统变量$@为空串,如果有错误,$@中为错误信息。
例子:$print = "print (\"hello,world\\n\");";
eval ($print);
结果输出:hello, world
函数名:system
调用语法:system(list)
解说:list中第一个元素为程序名,其余为参数。
system启动一个进程运行程序并等待其结束,程序结束后错误代码左移八位成为返回值。
例子:@proglist = ("echo", "hello,world!");
system(@proglist);
结果输出:hello, world!
函数名:fork
调用语法:procid = fork();
解说:创建程序的两个拷贝--父进程和子进程--同时运行。子进程返回零,父进程返回非零值,此值为子程序的进程ID号。
例子:$retval = fork();
if($retval == 0){
# this is the child process
exit; # this terminates the child process
}else{
# this is the parent process
}
结果输出:无
函数名:pipe
调用语法:pipe(infile, outfile);
解说:与fork合用,给父进程和子进程提供通信的方式。送到outfile文件变量的信息可以通过infile文件变量读取。步骤:
1、调用pipe
2、用fork将程序分成父进程和子进程
3、一个进程关掉infile,另一个关掉outfile
例子:pipe (INPUT,OUTPUT);
$retval = fork();
if ($retval != 0){
# this is the parent process
close (INPUT);
print ("Enter a line of input:\n");
$line = ;
print OUTPUT ($line);
}else{
# this is the child process
close (OUTPUT);
$line = ;
print ($line);
exit (0);
}
结果输出:$
program
Enter a line of input:
Here is a test line
Here is a test line
$
函数名:exec
调用语法:exec(list);
解说:与system类似,区别是启动新进程前结束当前程序。常与fork合用,当fork分成两个进程后,子进程用exec启动另一个程序。
例子:
结果输出:
函数名:syscall
调用语法:syscall(list);
解说:调用系统函数,list第一个元素是系统调用名,其余为参数。
如果参数是数字,就转化成C的整型数(type int)。否则传递字符串的指针。详见UNIX的帮助或Perl文档。
使用syscall必须包含文件syscall.pl,即:
require ("syscall.ph");
例子:
结果输出:
2、进程终止函数
函数名:die
调用语法:die(message);
解说:终止程序并向STDERR输出错误信息。message可以为字符串或列表。如果最后一个参数不包含换行符,则程序文件名和行号也被输出。
例子:die ("Cannot open input file");
结果输出:Cannot open input file at myprog line 6.
函数名:warn
调用语法:warn(message);
解说:与die类似,区别是不终止程序。
例子:warn("Danger! Danger!\n");
结果输出:Danger! Danger!
函数名:exit
调用语法:exit(retcode);
解说:终止程序并指定返回值。
例子:exit(2);
结果输出:无
函数名:kill
调用语法:kill(signal, proclist);
解说:给一组进程发送信号。
signal是发送的数字信号,9为杀掉进程。
proclist是进程ID列表。详见kill的UNIX帮助。
例子:
结果输出:
3、进程控制函数
函数名:sleep
调用语法:sleep(time);
解说:将程序暂停一段时间。time是停止的秒数。返回值为实际停止的秒数。
例子:sleep (5);
结果输出:无
函数名:wait
调用语法:procid = wait();
解说:暂停程序执行,等待子进程终止。
不需要参数,返回值为子进程ID,如果没有子进程,返回-1。
例子:
结果输出:
函数名:waitpid
调用语法:waitpid(procid, waitflag);
解说:暂停程序执行,等待特定的子进程终止。procid为等待的进程ID
例子:$procid = fork();
if ($procid == 0) {
# this is the child process
print ("this line is printed first\n");
exit(0);
} else {
# this is the parent process
waitpid ($procid, 0);
print ("this line is printed last\n");
}
结果输出:$ program
this line is printed first
this line is printed last
4、其它控制函数
函数名:caller
调用语法:subinfo = caller();
解说:返回调用者的程序名和行号,用于Perl Debugger。
返回值为三元素的列表:
1、调用处的包名
2、调用者文件名
3、调用处的行号
例子:
结果输出:
函数名:chroot
调用语法:chroot(dir);
解说:改变程序的根目录,详见chroot帮助。
例子:
结果输出:
函数名:local
调用语法:local($variable);
解说:在语句块(由大括号包围的语句集合)中定义局域变量,仅在此语句块中起作用,对其的改变不对块外同名变量造成影响。
千万不要在循环中使用,否则每次循环都定义一个新的局域变量!
例子:
结果输出:
函数名:times
调用语法:timelist = times
解说:返回该程序及所有子进程消耗的工作时间。
返回值为四个浮点数的列表:
1、程序耗用的用户时间
2、程序耗用的系统时间
3、子进程耗用的用户时间
4、子进程耗用的系统时间
例子:
结果输出:
二、数学函数
函数名:sin
调用语法:retval = sin(value);
解说:参数为弧度值。
函数名:cos
调用语法:retval = cos(value);
解说:参数为弧度值。
函数名:atan2
调用语法:retval = atan2(value1, value2);
解说:运算并返回value1除以value2结果的arctan值,单位为弧度,范围在-PI~PI。
应用例:
角度转化成弧度子程序。:sub degrees_to_radians {
local ($degrees) = @_;
local ($radians);11:
$radians = atan2(1,1) * $degrees / 45;
}
函数名:sqrt
调用语法:retval = sqrt(value);
解说:平方根函数。value为非负数。
函数名:exp
调用语法:retval = exp(value);
解说:返回e的value次方。
函数名:log
调用语法:retval = log(value);
解说:以e为底的自然对数。
函数名:abs
调用语法:retval = abs(value);
解说:绝对值函数(Perl 4中没有)。
函数名:rand
调用语法:retval = rand(num);
解说:随机数函数,返回0和整数num之间的一个浮点数。
函数名:srand
调用语法:srand (value);
解说:初始化随机数生成器。保证每次调用rand真正随机。
三、字符串处理函数
函数名:index
调用语法:position = index(string, substring, position);
解说:返回子串substring在字符串string中的位置,如果不存在则返回-1。参数position是可选项,表示匹配之前跳过的字符数,或者说从该位置开始匹配。
函数名:rindex
调用语法:position = rindex(string, substring, position);
解说:与index类似,区别是从右端匹配。
函数名:length
调用语法:num = length(string);
解说:返回字符串长度,或者说含有字符的数目。
函数名:pos
调用语法:offset = pos(string);
解说:返回最后一次模式匹配的位置。
函数名:substr
调用语法:substr(expr, skipchars, length)
解说:抽取字符串(或表达式生成的字符串)expr中的子串,跳过skipchars个字符,或者说从位置skipchars开始抽取子串(第一个字符位置为0),子串长度为length,此参数可忽略,意味着取剩下的全部字符。当此函数出现在等式左边时,expr必须为变量或数组元素,此时其中部分子串被等式右边的值替换。
函数名:study
调用语法:study(scalar);
解说:用一种内部格式提高变量的访问速度,同一时刻只对一个变量起作用。
函数名:lc,uc
调用语法:retval = lc(string);
retval = uc(string);
解说:将字符串全部转换成小/大写字母。
函数名:lcfirst,ucfirst
调用语法:retval = lcfirst(string);
retval = ucfirst(string);
解说:将第一个字母转换成小/大写。
函数名:quotameta
调用语法:newstring = quotemeta(oldstring);
解说:将非单词的字母前面加上反斜线(\)。
语句 : $string = quotemeta($string);
等效于:$string =~ s/(\W)/\\$1/g;
常用于模式匹配操作中,确保字符串中没有字符被看作匹配操作符。
函数名:join
调用语法:join(joinstr, list);
解说:把字符串列表(数组)组合成一个长的字符串,在每两个列表元素间插入串joinstr。
函数名:sprintf
调用语法:sprintf(string, fields);
解说:与printf类似,区别是结果不输出到文件,而作为返回值赋给变量。
例子:$num = 26;
$outstr = sprintf("%d = %x hexadecimal or %o octal\n",$num, $num, $num);
print ($outstr);
结果输出:26 = 1a hexadecimal or 32 octal
四、标量转换函数
函数名:chop
调用语法:$lastchar = chop(var);
解说:var可为变量或数组,当var为变量时,最后一个字符被删除并赋给$lastchar,当var为数组/列表时,所有元素的最后一个字符被删除,最后一个元素的最后一个字母赋给$lastchar。
函数名:chomp
调用语法:result = chomp(var);
解说:检查字符串或字符串列表中元素的最后一个字符是否为由系统变量$/定义的行分隔符,如果是就删除。返回值为实际删除的字符个数。
函数名:crypt
调用语法:result = crypt(original, salt);
解说:用DES算法加密字符串,original是将要加密的字符串,salt是两个字符的字符串,定义如何改变DES算法,以使更难解码。返回值为加密后的串。
函数名:hex
调用语法:decnum = hex(hexnum);
解说:将十六进制数(字符串形式)转化为十进制数。
函数名:int
调用语法:intnum = int(floatnum);
解说:将浮点数舍去小数部分转化为整型数。
函数名:oct
调用语法:decnum = oct(octnum);
解说:将八进制数(字符串形式)或十六进制数("0x.."形式)转化为十进制数。
函数名:ord
调用语法:asciival = ord(char);
解说:返回单个字符的ASCII值,与PASCAL中同名函数类似。
函数名:chr
调用语法:$char = chr(asciival);
解说:返回ASCII值的相应字符,与PASCAL中同名函数类似。
函数名:pack
调用语法:formatstr = pack(packformat, list);
解说:把一个列表或数组以在实际机器存贮格式或C等编程语言使用的格式转化(包装)到一个简单变量中。参数packformat包含一个或多个格式字符,列表中每个元素对应一个,各格式字符间可用空格或tab隔开,因为pack忽略空格。
除了格式a、A和@外,重复使用一种格式多次可在其后加个整数,如:
$twoints = pack ("i2", 103, 241);
把同一格式应用于所有的元素则加个*号,如:
$manyints = pack ("i*", 14, 26, 11, 83);
对于a和A而言,其后的整数表示要创建的字符串长度,重复方法如下:
$strings = pack ("a6" x 2, "test1", "test2");
格式@的情况比较特殊,其后必须加个整数,该数表示字符串必须的长度,如果长度不够,则用空字符(null)补足,如:
$output = pack ("a @6 a", "test", "test2");
pack函数最常见的用途是创建可与C程序交互的数据,例如C语言中字符串均以空字符(null)结尾,创建这样的数据可以这样做:
$Cstring = pack ("ax", $mystring);
下表是一些格式字符与C中数据类型的等价关系:
字符: 等价C数据类型
C:char
d:double
f:float
i:int
I:unsigned int (or unsigned)
l:long
L:unsigned long
s:short
S:unsigned short
完整的格式字符见下表。
格式字符: 描述
a:用空字符(null)补足的字符串
A:用空格补足的字符串
b:位串,低位在前
B:位串,高位在前
c:带符号字符(通常-128~127)
C:无符号字符(通常8位)
d:双精度浮点数
f:单精度浮点数
h:十六进制数串,低位在前
H:十六进制数串,高位在前
i:带符号整数
I:无符号整数
l:带符号长整数
L:无符号长整数
n:网络序短整数
N:网络序长整数
p:字符串指针
s:带符号短整数
S:无符号短整数
u:转化成uuencode格式
v:VAX序短整数
V:VAX序长整数
x:一个空字节
X:回退一个字节
@:以空字节(null)填充
函数名:unpack
调用语法:@list = unpack(packformat, formatstr);
解说:unpack 与pack功能相反,将以机器格式存贮的值转化成Perl中值的列表。其格式字符与pack基本相同(即上表),不同的有:A格式将机器格式字符串转化为 Perl字符串并去掉尾部所有空格或空字符;x为跳过一个字节;@为跳过一些字节到指定的位置,如@4为跳过4个字节。下面看一个@和X合同的例子::$longrightint = unpack ("@* X4 L", $packstring);
此语句将最后四个字节看作无符号长整数进行转化。下面看一个对uuencode文件解码的例子:
1 : #!/usr/local/bin/Perl
2 :
3 : open (CODEDFILE, "/u/janedoe/codefile") ||
4 : die ("Can't open input file");
5 : open (OUTFILE, ">outfile") ||
6 : die ("Can't open output file");
7 : while ($line = ) {
8 : $decoded = unpack("u", $line);
9 : print OUTFILE ($decoded);
10: }
11: close (OUTFILE);
12: close (CODEDFILE);
当将pack和unpack用于uuencode时,要记住,虽然它们与UNIX中的uuencode、uudecode工具算法相同,但并不提供首行和末行,如果想用uudecode对由pack的输出创建的文件进行解码,必须也把首行和末行输出(详见UNIX中uuencode帮助)。
函数名:vec
调用语法:retval = vec(vector, index, bits);
解说:顾名思义,vec即矢量(vector)函数,它把简单变量vector的值看作多块(维)数据,每块含一定数目的位,合起来即一个矢量数据。每次的调用访问其中一块数据,可以读取,也可以写入。参数index就象数组下标一样,提出访问哪一块,0为第一块,依次类推,要注意的是访问次序是从右到左的,即第一块在最右边。参数bits指定每块中的位数,可以为1,2,4,8,16或32。
例子:1 : #!/usr/local/bin/Perl
2 :
3 : $vector = pack ("B*", "11010011");
4 : $val1 = vec ($vector, 0, 4);
5 : $val2 = vec ($vector, 1, 4);
6 : print ("high-to-low order values: $val1 and $val2\n");
7 : $vector = pack ("b*", "11010011");
8 : $val1 = vec ($vector, 0, 4);
9 : $val2 = vec ($vector, 1, 4);
10: print ("low-to-high order values: $val1 and $val2\n");
结果:high-to-low order values: 3 and 13
low-to-high order values: 11 and 12
函数名:defined
调用语法:retval = defined(expr);
解说:判断一个变量、数组或数组的一个元素是否已经被赋值。expr为变量名、数组名或一个数组元素。如果已定义,返回真,否则返回假。
函数名:undef
调用语法:retval = undef(expr);
解说:取消变量、数组或数组元素甚至子程序的定义,回收其空间。返回值始终为未定义值,此值与空串等效。
五、数组和列表函数
函数名:grep
调用语法:@foundlist = grep(pattern, @searchlist);
解说:与同名的UNIX查找工具类似,grep函数在列表中抽取与指定模式匹配的元素,参数pattern为欲查找的模式,返回值是匹配元素的列表。
例子:@list = ("This", "is", "a", "test");
@foundlist = grep(/^[tT]/, @list);
结果:@foundlist = ("This", "test");
函数名:splice
调用语法:@retval = splice(@array, slipelements, length, @newlist);
解说: 拼接函数可以向列表(数组)中间插入元素、删除子列表或替换子列表。参数skipelements是拼接前跳过的元素数目,length是被替换的元素数,newlist是将要拼接进来的列表。当newlist的长度大于length时,后面的元素自动后移,反之则向前缩进。因此,当length=0 时,就相当于向列表中插入元素,而形如语句
splice (@array, -1, 0, "Hello");
则向数组末尾添加元素。而当newlist为空时就相当于删除子列表,这时如果length为空,就从第skipelements个元素后全部删除,而删除最后一个元素则为:splice (@array, -1);这种情况下,返回值为被删去的元素列表。
函数名:shift
调用语法:element = shift(@arrayvar);
解说:删去数组第一个元素,剩下元素前移,返回被删去的元素。不加参数时,缺省地对@ARGV进行操作。
函数名:unshift
调用语法:count = unshift(@arrayver, elements);
解说:作用与shift相反,在数组arrayvar开头增加一个或多个元素,返回值为结果(列表)的长度。等价于splice (@array, 0, 0, elements);
函数名:push
调用语法:push(@arrayvar, elements);
解说:在数组末尾增加一个或多个元素。等价于slice (@array, @array, 0, elements);
函数名:pop
调用语法:element = pop(@arrayvar);
解说:与push作用相反,删去列表最后一个元素,并将其作为返回值,当列表已空,则返回"未定义值"(即空串)。
函数名:split
调用语法:@list = split(pattern, string, maxlength);
解说:将字符串分割成一组元素的列表。每匹配一次pattern,就开始一个新元素,但pattern本身不包含在元素中。maxlength是可选项,当指定它时,达到该长度就不再分割。
函数名:sort
调用语法:@sorted = sort(@list);
解说:按字母次序给列表排序。
函数名:reverse
调用语法:@reversed = reverse(@list);
解说:按字母反序给列表排序。
函数名:map
调用语法:@resultlist = map(expr, @list);
解说:此函数在Perl5中定义,可以把列表中的各个元素作为表达式expr的操作数进行运算,其本身不改变,结果作为返回值。在表达式expr中,系统变量$_代表各个元素。
例子:1、@list = (100, 200, 300);
@results = map ($_+1, @list);
2、@results = map (&mysub($_), @list);
结果:
1、(101, 201, 301)
2、无
函数名:wantarray
调用语法:result = wantarray();
解说:Perl中,一些内置函数的行为根据其处理简单变量还是数组有所不同,如chop。自定义的子程序也可以定义这样两种行为。当子程序被期望返回列表时,此函数返回值为非零值(真),否则为零值(假)。
例子:1 : #!/usr/local/bin/Perl
2 :
3 : @array = &mysub();
4 : $scalar = &mysub();
5 :
6 : sub mysub {
7 : if (wantarray()) {
8 : print ("true\n");
9 : } else {
10: print ("false\n");
11: }
12: }
结果:$program
true
false
$
六、关联数组函数
函数名:keys
调用语法:@list = keys(%assoc_array);
解说:返回关联数组无序的下标列表。
函数名:values
调用语法:@list = values(%assoc_array);
解说:返回关联数组无序的值列表。
函数名:each
调用语法:@pair = each(%assoc_array);
解说:返回两个元素的列表--键值对(即下标和相应的值),同样无序。当关联数组已空,则返回空列表。
函数名:delete
调用语法:element = delete(assoc_array_item);
解说:删除关联数组中的元素,并将其值作为返回值。
例子:%array = ("foo", 26, "bar", 17");
$retval = delete ($array{"foo"});
结果:$retval = 26;
函数名:exists
调用语法:result = exists(element);
解说:在Perl5中定义,判断关联数组中是否存在某元素,若存在,返回非零值(真),否则返回零值(假)。
例子:$result = exists ($myarray{$mykey});
第二部分 Perl的CGI应用
第一章 cgilib例(未定稿)
一个简单的读取并处理表格请求数据的cgilib.pl例子:
#!/usr/bin/Perl
sub readGetData{
# 指定局部变量queryString用以保存和传递函数的参数
local(*queryString) = @_ if @_;
# 读取环境变量QUERY_STRING的值赋给变量$queryString
$queryString = $ENV{"QUERY_STRING"};
return 1;
}
sub readPostData{
local(*queryString)=@_ if @_;
local($contentLength);
# 读取环境变量CONTENT_LENGTH的值
$contentLength = $ENV{"CONTENT_LENGTH"};
# 检查是否有数据
if($contentLength){
# 从设备STDIN读取contentLength长度的字符赋给$queryString
read(STDIN,$queryString,$contentLength);
}
return 1;
}
sub readData{
local(*queryString) = @_ if @_;
# 读取环境变量REQUEST_METHOD
$requestType=$ENV{"REQUEST_METHOD"};
# 如果请求方式为GET则使用函数readGetData
# 否则如果请求方式为POST则使用函数readPostData
if($requestType eq "GET"){
&readGetData(*queryString);
}
elsif($requestType eq "POST"){
&readPostData(*queryString);
}
return 1;
}
sub DecodeData{
local(*queryString)= @_;
# 把加号转换成空格
$queryString=~s/\+/ /g;
# 转换十六进制字符
$queryString=~s/%(..)/pack("c",hex($1))/ge;
return 1;
}
sub parseData{
local(*queryString,*formData) = @_ if @_;
local($key,$value,$curString,@tmpArray);
# 以&为分隔符把字符串转换成键-值对
@tmpArray = split(/&/,$queryString);
# 在数组@tmpArray内循环
foreach $curString(@tmpArray){
# 以=为分隔符分开键-值对
($key,$value) = split(/=/,$curString);
# 解码
&DecodeData(*key);
&DecodeData(*value);
# 把键和值加到字典中
$formData{$key}=$value;
}
return 1;
}
#end of file cgilib.pl
使用方法:
要使用此库需含下列语句:
#require "cgilib.pl";
表格数据处理:
%dataDict=();
&readData(*data);
&parseData(*data,dataDict);
字典数据处理:
while(($key,$value)=each(%dataDict)){
print $key,"=",$value,"\n\n";
}
第二章 动态创建图象(未定稿)
"动态文档"不仅指文本,CGI程序可以创建图象、声音等各种媒体。你只须输出相应的MIME头、一行空行及原始数据即可。下例的image.cgi将装载一个GIF图像文件并送到浏览器显示:
$file = '/usr/local/etc/httpd/htdocs/images/picture.gif';
print "Content-Type: image/gif\n\n";
open(GIF,"<$file") || die "Can't open GIF\n";
while (read(GIF,$buffer,16384)) {
print $buffer;
}
image.cgi首先发送MIME头说明(Content-Type),然后读取文件内容并输出。这段程序对$file变量和Content-Type类型略加修改就可以发送声音或影像文件。
那么怎样把CGI程序创建的图像嵌到页面中呢?SSI是不行的,方法是用标签,语法如:
仅就显示picture.gif这幅图像而言,用上述的image.cgi是没有什么意义的,更恰当的方式是这样使用:
但image.cgi可以扩展功能来做更多的事。例如它可以从多个图像文件中随即地选择一个来显示,那么每一次访问该页面时都会出现不同的图像。计数器程序通常利用标签的这一特性,尤其是那些不允许解析HTML和SSI的服务器特别实用。服务器端不解析HTML文件可以降低服务器的负载。
此外,除了简单的装载并显示已有的图像文件外,可以真正的动态生成所需的图像。你可以设计一个CGI程序根据不同的参数及用户定义的一些细节来实 时创建相应的图像(如图表)并显示。这种程序的复杂性在于图像的生成而不在于将图像输出给浏览器。幸运的是,有一些库提供了这样的接口,如Thomas boutell的gd图像库,这是生成GIF图像的一个出色工具,可以从http://www.boutell.com/gd来下载。它是用C语言写的,但是有Perl接口库gd.pm,还有基于gd支持多种语言的接口库tgd和fly。详细情况见上述gd主页。
下面是一个简单的用位图动态生成图像的计数器程序,对理解上述描述应该会有所帮助(源代码下载)
您是第 位访问本网页的人。
附:GIF的说明
现在流行着一种趋势,即远离GIF格式而采用Portable Network Graphic格式(PNG),这种变化出于技术和法律两个方面的考虑。1995年1月1日,Unisys声称他们有权要求使用LZW压缩算法的软件公司要经过他们的许可或付给他们报酬,因为他们拥有专利使用权。而GIF格式正是使用这种算法。所以,你所编写的任何用于商业应用程序中的GIF图像(包括以 CGI/WWW为基础的那些文件),都必须购买许可证或支付费用。许多软件包不再压缩GIF文件(这样会使图像变得很大),或者把GIF文件及其支持软件全部去掉。PNG使用非专利的压缩算法,从而避免了这些麻烦。在技术方面,PNG提供了较好的压缩算法(无损失,像GIF一样,但不像JPEG。JPEG 在压缩时会丢失数据)、二维交互以及24位和48位真彩支持。现在,很少有浏览器支持内插的PNG图像,但不久的将来,这种情况很可能会改变。GD图形库 文件说明了PNG支持即将来临。
第三章 计数器的编写方法(未定稿)
一、记录(log)文件
1、grep
2、page-stats
3、wusage
二、创建自己的计数器
1、使用DBM文件
2、文本文件
3、文件锁定
4、输出计数结果
5、www Homepage Access Counter
6、使用GD图形库
计数器(Access Counter)可以记录网页被访问的次数,在万维网上的使用十分普遍,其编写方法很多,从简单的SSI命令到用CGI程序生成内嵌图像等。计数器除了记录点击次数外,还可以记录访问者的IP、OS、浏览器类型等内容,使你对自己网站的访问情况有个全面的了解,本章主要介绍点击次数的统计和显示方法。
一、记录(log)文件
1、grep
对于Web服务器而言,都有记录文件记录着详细的访问信息,其名称通常为access_log,下面是一个例子:
01: dialup-9.austin.io.com - - [02/Oct/1995:20:18:05 -0500] "GET /phoenix/ HTTP/1.0" 200 2330
02: crossnet.org - - [08/Oct/1995:19:56:45 -0500] "HEAD / HTTP/1.0" 200 0
03: dialup-2.austin.io.com - - [09/Oct/1995:07:54:56 -0500] "GET /leading-rein/orders HTTP/1.0" 401 -
04: onramp1-9.onr.com - - [10/Oct/1995:11:11:40 -0500] "GET / HTTP/1.0" 200 1529
05: onramp1-9.onr.com - - [10/Oct/1995:11:11:43 -0500] "GET /accn.jpg HTTP/1.0" 200 20342
06: onramp1-9.onr.com - - [10/Oct/1995:11:11:46 -0500] "GET /home.gif HTTP/1.0" 200 1331
07: dialup-3.austin.io.com - - [12/Oct/1995:08:04:27 -0500] "GET /cgi-bin/env.cgi?
08: SavedName=+&First+Name=Eric&Last+Name=Herrmann&Street=&City=&State=&
09: zip=&Phone+Number=%28999%29+999-9999+&Email+Address=&
10: simple=+Submit+Registration+ HTTP/1.0" 200 1261
11: dialup-20.austin.io.com - - [14/Oct/1995:16:40:04 -0500] "GET /leading-rein/index.cgi?unique_id=9658-199.170.89.58-813706781 HTTP/1.0" 200 1109
注;当主页在srm.conf中被命名为welcome.html、index.cgi、index.shtml等时,对其的访问记录,可能只含有目录名而不包含该文件名。
我们可以用UNIX命令grep来统计主页被访问的次数,grep命令通常输出每一行匹配结果,但可以加上参数-c以输出匹配行的数目,grep详见UNIX帮助。下面是一个简单的例子grep.cgi:
1: #!/usr/local/bin/Perl
2: print "content-type: text/html\n\n";
3: $num = `grep -c 'GET / HTTP' /your-server-root/logs/access_log` ;
4: $num += `grep -c 'GET /index.shtml' /your-server-root/logs/access_log` ;
5: $num += `grep -c 'GET /index.html' /your-server-root /logs/access_log` ;
6: print "$num\n";
现在就可以在主页中加上SSI指令来显示计数了,例如:
01:
02: grep test
03:
04:
05: This page has been accessed
06: times.
07:
08:
09:
别忘了把此文件扩展名改为.shtml。在grep.cgi中,grep命令中包围模式的单引号告诉UNIX shell不改变该串的内容以精确匹配。这种方法有许多缺陷,首先是效率低,用grep来匹配花时间较长,可能要几秒钟的时间,这对一个简单的文本计数器而言太长了。其次,对每一个需要计数器的 页面CGI文件均不相同。最后一个对某些人来说不算是个问题,就是要把Web服务器设置成允许SSI执行,即将其目录映射略加修改。
2、page-stats
有一个叫page-stats的程序较好地解决了grep的问题。它查看HTTP daemon的access_log并寻找在标识文件中指定网页的访问,然后计算其数目并生成一个HTML形式的统计页面。这样,你既得到了页面的详细统 计信息,同时又得到了可显示的结果页面,这样的例子可在http://www.sci.kun.nl/thalia/page-stats/page-stats_sci.html找到。还可以用grep命令在统计页面中查找所需信息并生成自己的显示形式,这样速度就快多了。
注意不应在建立自己的统计时运行该程序,否则会导致冲突。应该把它放到任务列表中用UNIX命令cron定时执行,每天、每小时甚至每几分钟运行一次。cron详见UNIX帮助。
3、wusage
另外一个广为应用的服务器统计程序是由Thomas Boutell(boutell@boutell.com)编写的应用于整个服务器的wusage,它生成很详细的信息,包括服务器怎样、何时及从何处被访问等等。它每周运行一次,可以生成漂亮的图表结果,十分直观。
使用wrsage要求使用ncSA或CERN的Web Server或任何有标准记录文件格式的服务器,还需要有C编译器,wusage可在www.boutell.com得到。随着时间推移,access_file会越来越庞大,必须定期截留,这时先查看最近一周wusage是否已生成了完整的报表,确定统计结束时间,然后把access_log中该时间前的访问记录删掉,并把wusage生成的结果保存在一个目录中,以便wusage可以生成过去访问情况的图表。
二、创建自己的计数器
除了使用access_log记录文件外,我们可以创建自己的计数器。这时首先必须决定用何种形式存贮计数结果,是用文本文件还是用DBM文件,然后要决定是否进行文件同步访问的保护,这是用文件锁定来实现的,最后就是确定数据的存贮格式了。
1、使用DBM文件
对DBM文件而言,常用的函数有dbmopen()、dbmclose()、reset()、each()、values()和keys(),用于计数器时,主要使用前两个函数。dbmopen()函数把DBM文件与关联数组绑定,调用语法为:
dbmopen (%array_name, DB_filename, Read_write_mode);
如果这时指定的数据库文件不存在,则自动创建两个名为DB_filename.dir和DB_filename.pag的文件,除非把读写模式设为undef值。
缺省的,只有64个记录被读进内存,可以通过给%array_name分配大小来改变此缺省值。如果你只是给自己的网页做计数,缺省值已经足够了,但如果是给整个服务器建立计数器,一般需要更大的值。
现在看看这三个参数。当调用dbmopen时,%array_name原有的值都被清除(如果有的话),用DBM文件中的值替换掉,给之赋予新值 很简单:$array_name{'new_key'} = value; 当调用dbmclose (%array_name);语句时绑定被解除,关联数组中的内容被写如DBM文件,也可以不关闭文件而将内容写入,方法是调用reset (%array_name);语句,注意此语句并不是重置DBM文件,而是将内存中的数据写入文件。第二个参数DB_filename是不包含扩展名的, 至于读写模式详见本教程的语言部分。
下面是个使用DBM文件的计数器的简单例子:
1: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
2: if(!(defined($counters{'my_counter'})){
3: $counters{'my_counter'}=0;}
4: $counters{'my_counter'})++;
5: $count=$counters{'my_counter'};
6: dbmclose (counters);
2、文本文件
如果不用DBM文件而用文本文件,除了打开、关闭文件外,还要涉及到数据的读写问题,必须确定合适的数据格式,基本步骤如下:
1)打开文件
2)读取计数
3)自增
4)写入新值
5)关闭文件
3、文件锁定
当更新文件内容时,该文件可能同时被另一个进程修改。对计数器程序而言,如果两个或多个人同时访问页面调用了计数器程序,就会出现多个进程同时修 改同一文件的情况,这样有的进程的修改就会失效。当然这并不是太大的问题,只是失去一些计数而已,不过计数器就不准确了,访问的人越多,这个问题就越大。解决办法就是修改时通知其它试图打开该文件的进程等待,或叫文件锁定,修改完再释放,允许其它进程打开文件并修改。有两种方法,一是创建自己的锁定机制, 一种是使用系统函数flock()。
1)创建自己的文件锁
这种方法具体实现是创建和删除一个特定名称的文件,这在资源共享机制中通常称作semaphore。下面是个例子:
01: While(-f counter.lock){
02: select(undef,undef,undef,0.1);}
03: open(LOCKFILE,">counter.lock);
04: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
05: if(!(defined($counters{'my_counter'})){
06: $counters{'my_counter'}=0;}
07: $counters{'my_counter'})++;
08: $count=$counters{'my_counter'};
09: dbmclose (counters);
10: close(LOCKFILE);
11: unlink(counter.lock);
首先检查锁定标志文件是否存在,如果存在,就说明另一个进程正在使用该文件,于是等待直到该文件(此处命名为counter.lock)不存在为 止。此处用select()的特殊形式循环等待,此语句使程序进入休眠状态一段时间,该时间段由最后一个参数定义。之所以不用sleep()函数是因为其 基本单位为秒,对这种文件锁定而言太长了,几个微秒就足够了。
当锁定标志文件不再存在,就创建自己的锁定标志文件并开始修改计数,完成后关闭该文件并用unlink函数将之删除,这样其它的进程又被允许修改计数。锁定标志文件并不是特殊的文件,其文件名也可以由你自己随意选择。
2)使用flock()
其实锁定文件是很普通的编程步骤,系统函数flock()提供了这一功能,如果在你的系统上不提供的话,可以使用前面介绍的方法自己实现。
flock()的语法为:
flock (filehandle, lock_type);
参数filehandle为用open()函数打开的文件句柄,lock_type可以为下面四个值之一:
1:定义共享锁。对计数器而言不适用。
2:定义排他锁。
3:定义非阻止锁。此处亦不用。
4:解除锁定。
使用flock()实现的文件锁定例子如下:
1a: dbmopen(%counters,"filename", 0666);
or
1b: OPEN(counters,"<filename")'
2: flock(counters,2);
3: if(!(defined($counters{'my_counter'})){
4: $counters{'my_counter'}=0;}
5: $counters{'my_counter'})++;
6: $count=$counters{'my_counter'};
7: dbmclose (counters);
8: flock(counters,8);
4、输出计数结果
现在一切就绪,只剩下输出我们的计数结果了,有三种输出方法:
1)用上面谈到的SSI方法输出。
2)创建各种文本格式输出。
3)生成各种漂亮的图形结果输出,本教程的《动态创建图像》一章讲述了基本原理并提供了一个x-bitmap格式的小例子,下面介绍两个更完善和漂亮的程序/库,这两个例子均需要C编译器。
5、www Homepage Access Counter
这是一个广为应用的网页计数程序,它利用已有的GIF图象连接起来生成一个GIF图象,此程序是用C语言写的,有适用于各种操作系统的版本,可以在www.fccc.edu/users/muquit/Count.html下载。它提供了很多参数,功能比较齐全,生成的图象结果也很漂亮,可以选择图像格式,其自带了一些数字样式,但你可以增加自己的数字图像生成各种想要的图像,cervantes.comptons.com/digits/digits.htm提供了很多GIF数字图象。其参数通过QUERY_STRING传递,且必须是小写字母,下面是个较复杂的调用例子:
其参数详细说明和使用方法详见上述下载网址。如果有必要的话,研究并修改一下其源程序可以使你生成更适合于自己需要的图象。
6、使用GD图形库
Counter利用现有的数字图象简化了一部分的工作,其目的就是用于图形计数器。GD图形库的功能更加强大,不仅可以用于创建图形计数器,还可以生成各 种统计图表,还提供了Perl接口库。GD及其衍生的程序详见本教程《动态创建图像》一章。
在下载的程序中有一个名为gddemo.c的程序演示了其使用方法,在sparke.cs.nyu.edu:8086/cgi.htm有其用于计数器的例子。下面是一个通过GD.pm调用GD图形库生成图像的Perl程序例:
use GD;
# create a new image
$im = new GD:Image(100,100);
# allocate some colors
$white = $im->colorAllocate(255,255,255);
$black = $im->colorAllocate(0,0,0);
$red = $im->colorAllocate(255,0,0);
$blue = $im->colorAllocate(0,0,255);
# make the background transparent and interlaced
$im->transparent($white);
$im->interlaced('true');
# Put a black frame around the picture
$im->rectangle(0,0,99,99,$black);
# Draw a blue oval
$im->arc(50,50,95,75,0,360,$blue);
# And fill it with red
$im->fill(50,50,$red);
# Convert the image to GIF and print it on standard output
print $im->gif;
编程标准与错误处理
函数集(未定稿)
一、进程处理函数
1、进程启动函数
2、进程终止函数
3、进程控制函数
4、其它控制函数
二、数学函数
三、字符串处理函数
四、标量转换函数
五、数组和列表函数
六、关联数组函数
编程标准
每个程序员都有他自己的喜好格式化的方法,但也有一些通用的准则,会让你的程序更容易阅读,理解和维护。最重要的是-w标志在任何时间下运行程序。你可以把它明确地没有任何警告杂注或$^W变量通过特定部分的代码,如果你一定要。你也应该总是运行在使用严格的或知道的原因下,为什么不呢。使用sigtrap ,甚至使用诊断程序的编译指示也可能是有用的。
关于美学的代码布局,的唯一强烈的Larry关心的是,多行块的右大括号排队的关键字开始构建。 除此之外,他还有其他的喜好是没有那么强的:
4空格缩进。
如果可能的话,左大作为关键字在同一行上,否则按顺序。
空格句前花多行块。
一个行块可以被放在同一行,包括花括号。
不要有空格在分号前。
在"short"一行BLOCK分号省略。
大多数操作符两边要用空格.
一个"复杂的"下标(括号内)两边要用空格.
B块之间空行,做不同的事情。
不要空挂else。
函数名称和左括号之间不要有空格 。
每个逗号后要有空格。
长的行折断后操作员(除了和或(or,and))。
当前行的最后一个括号匹配后空格。
垂直行相应的项(条)目。
只要清晰就不会有麻烦省略多余的标点符号。
这里有一些其他风格更实质性的问题需要考虑:
仅仅因为你能做一些事情并不意味着我应该以一种特殊的方式做。Perl是设计给你几种方法可以做任何事情,所以考虑选择最可读的。例如:
open(FOO,$foo) || die "Can't open $foo: $!";
是比以下要的好:
die "Can't open $foo: $!" unless open(FOO,$foo);
因为第二种方式隐藏的修饰符声明的主要观点。另一方面
print "Starting analysis\n" if $verbose;
明显优于
$verbose && print "Starting analysis\n";
因为主要的问题不在于用户输入的-v或不是。不要去愚蠢的扭曲退出循环的顶部或底部,当Perl提供了最后的运算符,这样你就可以在中间退出。"减少缩进"这一点,使之更可见:
LINE:
for (;;) {
statements;
last LINE if $foo;
next LINE if /^#/;
statements;
}
不要害怕使用循环标记 - 它们以提高可读性以及允许多级循环中断。见前面的例子。
在真空环境中,避免使用的grep()(或map())或`反引号',也就是说,当你抛弃它们的返回值,所有这些功能有返回值使用它们。否则使用foreach()循环或system()函数代替。
对于便携性,使用时无法在每一台机器上实现的功能,测试在一个eval的构建体,看它是否失败。如果你知道是什么版本或最接近的一个特定的功能已实现,可以测试 $] ($Perl_VERSION)去看它是否存在。配置模块还可以让你询问的配置程序安装Perl的时确定的值。
选择助记符标识符。如果你不记得助记符意味着什么,你已经有了一个问题。
虽然短标识符如$gotit,或许没问题,用下划线来分隔单词的较长的标识符。这是通常$var_names_like_this比$VarNamesLikeThis比较容易理解,尤其是对于非英语母语的人。这也是一个简单的规则作用和VAR_NAMES_LIKE_THIS一致。
有时,包名是这个规则的一个例外。Perl非正式保留小写的模块名称为"杂"模块,如整数及严格。其他模块应以大写字母开始,并使用大小写混合,但可能没有强调由于限制在原始文件系统的模块名作为文件必须符合一些稀疏的字节表示的。
如果你有一个真正的复杂正则表达式,使用/x修饰符,并在一些空白,使其看上去不像行噪声。 不要使用斜杠作为分隔符,当你的正则表达式有斜杠或反斜杠。
请务必检查系统调用的返回代码。好的错误消息应该到STDERR,包括哪些程序引起的问题,什么是失败的系统调用和参数,(非常重要)标准的系统错误消息应包含什么地方出了错。这里有一个简单但足够的例子:
opendir(D, $dir) or die "can't opendir $dir: $!";
想想可重用性。为什么要浪费脑力上的一个一次性时,你可能想再次做类似的东西吗?概括你的代码。考虑写一个模块或对象类。考虑使你的代码干净,使用严格的警告和使用(或-w)有效运行。考虑放弃你的代码。考虑改变你的整个世界观。试想一下......哦,没关系。
错误处理
warn函数
警告函数只是提出了一个警告,一个消息被打印到STDERR,但没有采取进一步的行动。
chdir('/etc') or warn "Can't change directory";
die函数
die函数的工作原理就像警告,但它也调用exit。在一个正常的脚本,这个函数有立即终止执行的效果。
chdir('/etc') or die "Can't change directory";
模块中的错误报告
有两种不同的情况,我们需要能够处理:
报告错误的模块中引用的模块的文件名和行号 - 这是非常有用的调试模块时,或者当您特别希望提出一个模块相关的,而不是脚本相关的,错误的。
在一个模块中,引用了调用者的信息,以便可以调试行内的脚本导致错误的错误报告。在这种方式中引发的错误最终用户是非常有用的,因为它们在调用脚本的起源突出显示错误。
警告和die函数正常工作方式略有不同,比你期望从一个模块内调用。例如,简单的模块:
package T;
require Exporter;
@ISA = qw/Exporter/;
@EXPORT = qw/function/;
use Carp;
# by www.gitbook.net
sub function{
warn "Error in module!";
}
1;
从脚本调用时,
use T;
function();
It will produce following result
Error in module! at T.pm line 11.
这个是多还是少,你所期望的,但不一定是你想要的。从一个模块程序员的角度来看,信息是有用的,因为它有助于以指向一个错误所在模块的本身。为最终用户提供的信息是相当无用的,但硬化的程序员,它完全没有意义的。
对于这样的问题的解决方案是的Carp 模块,它提供了一个简单的方法报告错误返回调用脚本的信息模块内。Carp模块提供了四个函数:carp, cluck, croak, confess. 这些功能将在下面讨论。
Carp函数
carp 函数是基本等效的警告,并没有真正退出脚本和打印脚本的名称打印到STDERR的信息。
carp "Error in module!";
#by www.gitbook.net
This would result in
Error in module! at test.pl line 3
Cluck函数
cluck 函数是一种增压carp, 它遵循相同的基本原则,而且打印的所有模块,导致被调用的函数的堆栈跟踪,包括信息的原始脚本。
cluck "Error in module!";
This would result in something like
Error in module! at T.pm line 11
T::function() called at S.pm line 13
S::raise() called at test.pl line 3
Croak函数
croak 函数是相当于die,但它报告的调用上一级。就像die,此功能也可以退出脚本报告错误STDERR:
croak "Definitely didn't work";
This would result in
Error in module! at S.pm line 13
和carp相同的基本规则适用于包括行和文件信息的警告和死亡的函数。
Confess函数
confess 功能是一样cluck,它调用die,然后打印一个堆栈跟踪所有的方式的产生脚本。
confess "Failed around about there";
This would result in
Error in module! at T.pm line 11
T::function() called at S.pm line 13
S::raise() called at t2.pl line 3
一、进程处理函数
1、进程启动函数
函数名:eval
调用语法:eval(string)
解说:将string看作Perl语句执行。
正确执行后,系统变量$@为空串,如果有错误,$@中为错误信息。
例子:$print = "print (\"hello,world\\n\");";
eval ($print);
结果输出:hello, world
函数名:system
调用语法:system(list)
解说:list中第一个元素为程序名,其余为参数。
system启动一个进程运行程序并等待其结束,程序结束后错误代码左移八位成为返回值。
例子:@proglist = ("echo", "hello,world!");
system(@proglist);
结果输出:hello, world!
函数名:fork
调用语法:procid = fork();
解说:创建程序的两个拷贝--父进程和子进程--同时运行。子进程返回零,父进程返回非零值,此值为子程序的进程ID号。
例子:$retval = fork();
if($retval == 0){
# this is the child process
exit; # this terminates the child process
}else{
# this is the parent process
}
结果输出:无
函数名:pipe
调用语法:pipe(infile, outfile);
解说:与fork合用,给父进程和子进程提供通信的方式。送到outfile文件变量的信息可以通过infile文件变量读取。步骤:
1、调用pipe
2、用fork将程序分成父进程和子进程
3、一个进程关掉infile,另一个关掉outfile
例子:pipe (INPUT,OUTPUT);
$retval = fork();
if ($retval != 0){
# this is the parent process
close (INPUT);
print ("Enter a line of input:\n");
$line = ;
print OUTPUT ($line);
}else{
# this is the child process
close (OUTPUT);
$line = ;
print ($line);
exit (0);
}
结果输出:$
program
Enter a line of input:
Here is a test line
Here is a test line
$
函数名:exec
调用语法:exec(list);
解说:与system类似,区别是启动新进程前结束当前程序。常与fork合用,当fork分成两个进程后,子进程用exec启动另一个程序。
例子:
结果输出:
函数名:syscall
调用语法:syscall(list);
解说:调用系统函数,list第一个元素是系统调用名,其余为参数。
如果参数是数字,就转化成C的整型数(type int)。否则传递字符串的指针。详见UNIX的帮助或Perl文档。
使用syscall必须包含文件syscall.pl,即:
require ("syscall.ph");
例子:
结果输出:
2、进程终止函数
函数名:die
调用语法:die(message);
解说:终止程序并向STDERR输出错误信息。message可以为字符串或列表。如果最后一个参数不包含换行符,则程序文件名和行号也被输出。
例子:die ("Cannot open input file");
结果输出:Cannot open input file at myprog line 6.
函数名:warn
调用语法:warn(message);
解说:与die类似,区别是不终止程序。
例子:warn("Danger! Danger!\n");
结果输出:Danger! Danger!
函数名:exit
调用语法:exit(retcode);
解说:终止程序并指定返回值。
例子:exit(2);
结果输出:无
函数名:kill
调用语法:kill(signal, proclist);
解说:给一组进程发送信号。
signal是发送的数字信号,9为杀掉进程。
proclist是进程ID列表。详见kill的UNIX帮助。
例子:
结果输出:
3、进程控制函数
函数名:sleep
调用语法:sleep(time);
解说:将程序暂停一段时间。time是停止的秒数。返回值为实际停止的秒数。
例子:sleep (5);
结果输出:无
函数名:wait
调用语法:procid = wait();
解说:暂停程序执行,等待子进程终止。
不需要参数,返回值为子进程ID,如果没有子进程,返回-1。
例子:
结果输出:
函数名:waitpid
调用语法:waitpid(procid, waitflag);
解说:暂停程序执行,等待特定的子进程终止。procid为等待的进程ID
例子:$procid = fork();
if ($procid == 0) {
# this is the child process
print ("this line is printed first\n");
exit(0);
} else {
# this is the parent process
waitpid ($procid, 0);
print ("this line is printed last\n");
}
结果输出:$ program
this line is printed first
this line is printed last
4、其它控制函数
函数名:caller
调用语法:subinfo = caller();
解说:返回调用者的程序名和行号,用于Perl Debugger。
返回值为三元素的列表:
1、调用处的包名
2、调用者文件名
3、调用处的行号
例子:
结果输出:
函数名:chroot
调用语法:chroot(dir);
解说:改变程序的根目录,详见chroot帮助。
例子:
结果输出:
函数名:local
调用语法:local($variable);
解说:在语句块(由大括号包围的语句集合)中定义局域变量,仅在此语句块中起作用,对其的改变不对块外同名变量造成影响。
千万不要在循环中使用,否则每次循环都定义一个新的局域变量!
例子:
结果输出:
函数名:times
调用语法:timelist = times
解说:返回该程序及所有子进程消耗的工作时间。
返回值为四个浮点数的列表:
1、程序耗用的用户时间
2、程序耗用的系统时间
3、子进程耗用的用户时间
4、子进程耗用的系统时间
例子:
结果输出:
二、数学函数
函数名:sin
调用语法:retval = sin(value);
解说:参数为弧度值。
函数名:cos
调用语法:retval = cos(value);
解说:参数为弧度值。
函数名:atan2
调用语法:retval = atan2(value1, value2);
解说:运算并返回value1除以value2结果的arctan值,单位为弧度,范围在-PI~PI。
应用例:
角度转化成弧度子程序。:sub degrees_to_radians {
local ($degrees) = @_;
local ($radians);11:
$radians = atan2(1,1) * $degrees / 45;
}
函数名:sqrt
调用语法:retval = sqrt(value);
解说:平方根函数。value为非负数。
函数名:exp
调用语法:retval = exp(value);
解说:返回e的value次方。
函数名:log
调用语法:retval = log(value);
解说:以e为底的自然对数。
函数名:abs
调用语法:retval = abs(value);
解说:绝对值函数(Perl 4中没有)。
函数名:rand
调用语法:retval = rand(num);
解说:随机数函数,返回0和整数num之间的一个浮点数。
函数名:srand
调用语法:srand (value);
解说:初始化随机数生成器。保证每次调用rand真正随机。
三、字符串处理函数
函数名:index
调用语法:position = index(string, substring, position);
解说:返回子串substring在字符串string中的位置,如果不存在则返回-1。参数position是可选项,表示匹配之前跳过的字符数,或者说从该位置开始匹配。
函数名:rindex
调用语法:position = rindex(string, substring, position);
解说:与index类似,区别是从右端匹配。
函数名:length
调用语法:num = length(string);
解说:返回字符串长度,或者说含有字符的数目。
函数名:pos
调用语法:offset = pos(string);
解说:返回最后一次模式匹配的位置。
函数名:substr
调用语法:substr(expr, skipchars, length)
解说:抽取字符串(或表达式生成的字符串)expr中的子串,跳过skipchars个字符,或者说从位置skipchars开始抽取子串(第一个字符位置为0),子串长度为length,此参数可忽略,意味着取剩下的全部字符。当此函数出现在等式左边时,expr必须为变量或数组元素,此时其中部分子串被等式右边的值替换。
函数名:study
调用语法:study(scalar);
解说:用一种内部格式提高变量的访问速度,同一时刻只对一个变量起作用。
函数名:lc,uc
调用语法:retval = lc(string);
retval = uc(string);
解说:将字符串全部转换成小/大写字母。
函数名:lcfirst,ucfirst
调用语法:retval = lcfirst(string);
retval = ucfirst(string);
解说:将第一个字母转换成小/大写。
函数名:quotameta
调用语法:newstring = quotemeta(oldstring);
解说:将非单词的字母前面加上反斜线(\)。
语句 : $string = quotemeta($string);
等效于:$string =~ s/(\W)/\\$1/g;
常用于模式匹配操作中,确保字符串中没有字符被看作匹配操作符。
函数名:join
调用语法:join(joinstr, list);
解说:把字符串列表(数组)组合成一个长的字符串,在每两个列表元素间插入串joinstr。
函数名:sprintf
调用语法:sprintf(string, fields);
解说:与printf类似,区别是结果不输出到文件,而作为返回值赋给变量。
例子:$num = 26;
$outstr = sprintf("%d = %x hexadecimal or %o octal\n",$num, $num, $num);
print ($outstr);
结果输出:26 = 1a hexadecimal or 32 octal
四、标量转换函数
函数名:chop
调用语法:$lastchar = chop(var);
解说:var可为变量或数组,当var为变量时,最后一个字符被删除并赋给$lastchar,当var为数组/列表时,所有元素的最后一个字符被删除,最后一个元素的最后一个字母赋给$lastchar。
函数名:chomp
调用语法:result = chomp(var);
解说:检查字符串或字符串列表中元素的最后一个字符是否为由系统变量$/定义的行分隔符,如果是就删除。返回值为实际删除的字符个数。
函数名:crypt
调用语法:result = crypt(original, salt);
解说:用DES算法加密字符串,original是将要加密的字符串,salt是两个字符的字符串,定义如何改变DES算法,以使更难解码。返回值为加密后的串。
函数名:hex
调用语法:decnum = hex(hexnum);
解说:将十六进制数(字符串形式)转化为十进制数。
函数名:int
调用语法:intnum = int(floatnum);
解说:将浮点数舍去小数部分转化为整型数。
函数名:oct
调用语法:decnum = oct(octnum);
解说:将八进制数(字符串形式)或十六进制数("0x.."形式)转化为十进制数。
函数名:ord
调用语法:asciival = ord(char);
解说:返回单个字符的ASCII值,与PASCAL中同名函数类似。
函数名:chr
调用语法:$char = chr(asciival);
解说:返回ASCII值的相应字符,与PASCAL中同名函数类似。
函数名:pack
调用语法:formatstr = pack(packformat, list);
解说:把一个列表或数组以在实际机器存贮格式或C等编程语言使用的格式转化(包装)到一个简单变量中。参数packformat包含一个或多个格式字符,列表中每个元素对应一个,各格式字符间可用空格或tab隔开,因为pack忽略空格。
除了格式a、A和@外,重复使用一种格式多次可在其后加个整数,如:
$twoints = pack ("i2", 103, 241);
把同一格式应用于所有的元素则加个*号,如:
$manyints = pack ("i*", 14, 26, 11, 83);
对于a和A而言,其后的整数表示要创建的字符串长度,重复方法如下:
$strings = pack ("a6" x 2, "test1", "test2");
格式@的情况比较特殊,其后必须加个整数,该数表示字符串必须的长度,如果长度不够,则用空字符(null)补足,如:
$output = pack ("a @6 a", "test", "test2");
pack函数最常见的用途是创建可与C程序交互的数据,例如C语言中字符串均以空字符(null)结尾,创建这样的数据可以这样做:
$Cstring = pack ("ax", $mystring);
下表是一些格式字符与C中数据类型的等价关系:
字符: 等价C数据类型
C:char
d:double
f:float
i:int
I:unsigned int (or unsigned)
l:long
L:unsigned long
s:short
S:unsigned short
完整的格式字符见下表。
格式字符: 描述
a:用空字符(null)补足的字符串
A:用空格补足的字符串
b:位串,低位在前
B:位串,高位在前
c:带符号字符(通常-128~127)
C:无符号字符(通常8位)
d:双精度浮点数
f:单精度浮点数
h:十六进制数串,低位在前
H:十六进制数串,高位在前
i:带符号整数
I:无符号整数
l:带符号长整数
L:无符号长整数
n:网络序短整数
N:网络序长整数
p:字符串指针
s:带符号短整数
S:无符号短整数
u:转化成uuencode格式
v:VAX序短整数
V:VAX序长整数
x:一个空字节
X:回退一个字节
@:以空字节(null)填充
函数名:unpack
调用语法:@list = unpack(packformat, formatstr);
解说:unpack 与pack功能相反,将以机器格式存贮的值转化成Perl中值的列表。其格式字符与pack基本相同(即上表),不同的有:A格式将机器格式字符串转化为 Perl字符串并去掉尾部所有空格或空字符;x为跳过一个字节;@为跳过一些字节到指定的位置,如@4为跳过4个字节。下面看一个@和X合同的例子::$longrightint = unpack ("@* X4 L", $packstring);
此语句将最后四个字节看作无符号长整数进行转化。下面看一个对uuencode文件解码的例子:
1 : #!/usr/local/bin/Perl
2 :
3 : open (CODEDFILE, "/u/janedoe/codefile") ||
4 : die ("Can't open input file");
5 : open (OUTFILE, ">outfile") ||
6 : die ("Can't open output file");
7 : while ($line = ) {
8 : $decoded = unpack("u", $line);
9 : print OUTFILE ($decoded);
10: }
11: close (OUTFILE);
12: close (CODEDFILE);
当将pack和unpack用于uuencode时,要记住,虽然它们与UNIX中的uuencode、uudecode工具算法相同,但并不提供首行和末行,如果想用uudecode对由pack的输出创建的文件进行解码,必须也把首行和末行输出(详见UNIX中uuencode帮助)。
函数名:vec
调用语法:retval = vec(vector, index, bits);
解说:顾名思义,vec即矢量(vector)函数,它把简单变量vector的值看作多块(维)数据,每块含一定数目的位,合起来即一个矢量数据。每次的调用访问其中一块数据,可以读取,也可以写入。参数index就象数组下标一样,提出访问哪一块,0为第一块,依次类推,要注意的是访问次序是从右到左的,即第一块在最右边。参数bits指定每块中的位数,可以为1,2,4,8,16或32。
例子:1 : #!/usr/local/bin/Perl
2 :
3 : $vector = pack ("B*", "11010011");
4 : $val1 = vec ($vector, 0, 4);
5 : $val2 = vec ($vector, 1, 4);
6 : print ("high-to-low order values: $val1 and $val2\n");
7 : $vector = pack ("b*", "11010011");
8 : $val1 = vec ($vector, 0, 4);
9 : $val2 = vec ($vector, 1, 4);
10: print ("low-to-high order values: $val1 and $val2\n");
结果:high-to-low order values: 3 and 13
low-to-high order values: 11 and 12
函数名:defined
调用语法:retval = defined(expr);
解说:判断一个变量、数组或数组的一个元素是否已经被赋值。expr为变量名、数组名或一个数组元素。如果已定义,返回真,否则返回假。
函数名:undef
调用语法:retval = undef(expr);
解说:取消变量、数组或数组元素甚至子程序的定义,回收其空间。返回值始终为未定义值,此值与空串等效。
五、数组和列表函数
函数名:grep
调用语法:@foundlist = grep(pattern, @searchlist);
解说:与同名的UNIX查找工具类似,grep函数在列表中抽取与指定模式匹配的元素,参数pattern为欲查找的模式,返回值是匹配元素的列表。
例子:@list = ("This", "is", "a", "test");
@foundlist = grep(/^[tT]/, @list);
结果:@foundlist = ("This", "test");
函数名:splice
调用语法:@retval = splice(@array, slipelements, length, @newlist);
解说: 拼接函数可以向列表(数组)中间插入元素、删除子列表或替换子列表。参数skipelements是拼接前跳过的元素数目,length是被替换的元素数,newlist是将要拼接进来的列表。当newlist的长度大于length时,后面的元素自动后移,反之则向前缩进。因此,当length=0 时,就相当于向列表中插入元素,而形如语句
splice (@array, -1, 0, "Hello");
则向数组末尾添加元素。而当newlist为空时就相当于删除子列表,这时如果length为空,就从第skipelements个元素后全部删除,而删除最后一个元素则为:splice (@array, -1);这种情况下,返回值为被删去的元素列表。
函数名:shift
调用语法:element = shift(@arrayvar);
解说:删去数组第一个元素,剩下元素前移,返回被删去的元素。不加参数时,缺省地对@ARGV进行操作。
函数名:unshift
调用语法:count = unshift(@arrayver, elements);
解说:作用与shift相反,在数组arrayvar开头增加一个或多个元素,返回值为结果(列表)的长度。等价于splice (@array, 0, 0, elements);
函数名:push
调用语法:push(@arrayvar, elements);
解说:在数组末尾增加一个或多个元素。等价于slice (@array, @array, 0, elements);
函数名:pop
调用语法:element = pop(@arrayvar);
解说:与push作用相反,删去列表最后一个元素,并将其作为返回值,当列表已空,则返回"未定义值"(即空串)。
函数名:split
调用语法:@list = split(pattern, string, maxlength);
解说:将字符串分割成一组元素的列表。每匹配一次pattern,就开始一个新元素,但pattern本身不包含在元素中。maxlength是可选项,当指定它时,达到该长度就不再分割。
函数名:sort
调用语法:@sorted = sort(@list);
解说:按字母次序给列表排序。
函数名:reverse
调用语法:@reversed = reverse(@list);
解说:按字母反序给列表排序。
函数名:map
调用语法:@resultlist = map(expr, @list);
解说:此函数在Perl5中定义,可以把列表中的各个元素作为表达式expr的操作数进行运算,其本身不改变,结果作为返回值。在表达式expr中,系统变量$_代表各个元素。
例子:1、@list = (100, 200, 300);
@results = map ($_+1, @list);
2、@results = map (&mysub($_), @list);
结果:
1、(101, 201, 301)
2、无
函数名:wantarray
调用语法:result = wantarray();
解说:Perl中,一些内置函数的行为根据其处理简单变量还是数组有所不同,如chop。自定义的子程序也可以定义这样两种行为。当子程序被期望返回列表时,此函数返回值为非零值(真),否则为零值(假)。
例子:1 : #!/usr/local/bin/Perl
2 :
3 : @array = &mysub();
4 : $scalar = &mysub();
5 :
6 : sub mysub {
7 : if (wantarray()) {
8 : print ("true\n");
9 : } else {
10: print ("false\n");
11: }
12: }
结果:$program
true
false
$
六、关联数组函数
函数名:keys
调用语法:@list = keys(%assoc_array);
解说:返回关联数组无序的下标列表。
函数名:values
调用语法:@list = values(%assoc_array);
解说:返回关联数组无序的值列表。
函数名:each
调用语法:@pair = each(%assoc_array);
解说:返回两个元素的列表--键值对(即下标和相应的值),同样无序。当关联数组已空,则返回空列表。
函数名:delete
调用语法:element = delete(assoc_array_item);
解说:删除关联数组中的元素,并将其值作为返回值。
例子:%array = ("foo", 26, "bar", 17");
$retval = delete ($array{"foo"});
结果:$retval = 26;
函数名:exists
调用语法:result = exists(element);
解说:在Perl5中定义,判断关联数组中是否存在某元素,若存在,返回非零值(真),否则返回零值(假)。
例子:$result = exists ($myarray{$mykey});
第二部分 Perl的CGI应用
第一章 cgilib例(未定稿)
一个简单的读取并处理表格请求数据的cgilib.pl例子:
#!/usr/bin/Perl
sub readGetData{
# 指定局部变量queryString用以保存和传递函数的参数
local(*queryString) = @_ if @_;
# 读取环境变量QUERY_STRING的值赋给变量$queryString
$queryString = $ENV{"QUERY_STRING"};
return 1;
}
sub readPostData{
local(*queryString)=@_ if @_;
local($contentLength);
# 读取环境变量CONTENT_LENGTH的值
$contentLength = $ENV{"CONTENT_LENGTH"};
# 检查是否有数据
if($contentLength){
# 从设备STDIN读取contentLength长度的字符赋给$queryString
read(STDIN,$queryString,$contentLength);
}
return 1;
}
sub readData{
local(*queryString) = @_ if @_;
# 读取环境变量REQUEST_METHOD
$requestType=$ENV{"REQUEST_METHOD"};
# 如果请求方式为GET则使用函数readGetData
# 否则如果请求方式为POST则使用函数readPostData
if($requestType eq "GET"){
&readGetData(*queryString);
}
elsif($requestType eq "POST"){
&readPostData(*queryString);
}
return 1;
}
sub DecodeData{
local(*queryString)= @_;
# 把加号转换成空格
$queryString=~s/\+/ /g;
# 转换十六进制字符
$queryString=~s/%(..)/pack("c",hex($1))/ge;
return 1;
}
sub parseData{
local(*queryString,*formData) = @_ if @_;
local($key,$value,$curString,@tmpArray);
# 以&为分隔符把字符串转换成键-值对
@tmpArray = split(/&/,$queryString);
# 在数组@tmpArray内循环
foreach $curString(@tmpArray){
# 以=为分隔符分开键-值对
($key,$value) = split(/=/,$curString);
# 解码
&DecodeData(*key);
&DecodeData(*value);
# 把键和值加到字典中
$formData{$key}=$value;
}
return 1;
}
#end of file cgilib.pl
使用方法:
要使用此库需含下列语句:
#require "cgilib.pl";
表格数据处理:
%dataDict=();
&readData(*data);
&parseData(*data,dataDict);
字典数据处理:
while(($key,$value)=each(%dataDict)){
print $key,"=",$value,"\n\n";
}
第二章 动态创建图象(未定稿)
"动态文档"不仅指文本,CGI程序可以创建图象、声音等各种媒体。你只须输出相应的MIME头、一行空行及原始数据即可。下例的image.cgi将装载一个GIF图像文件并送到浏览器显示:
$file = '/usr/local/etc/httpd/htdocs/images/picture.gif';
print "Content-Type: image/gif\n\n";
open(GIF,"<$file") || die "Can't open GIF\n";
while (read(GIF,$buffer,16384)) {
print $buffer;
}
image.cgi首先发送MIME头说明(Content-Type),然后读取文件内容并输出。这段程序对$file变量和Content-Type类型略加修改就可以发送声音或影像文件。
那么怎样把CGI程序创建的图像嵌到页面中呢?SSI是不行的,方法是用标签,语法如:
仅就显示picture.gif这幅图像而言,用上述的image.cgi是没有什么意义的,更恰当的方式是这样使用:
但image.cgi可以扩展功能来做更多的事。例如它可以从多个图像文件中随即地选择一个来显示,那么每一次访问该页面时都会出现不同的图像。计数器程序通常利用标签的这一特性,尤其是那些不允许解析HTML和SSI的服务器特别实用。服务器端不解析HTML文件可以降低服务器的负载。
此外,除了简单的装载并显示已有的图像文件外,可以真正的动态生成所需的图像。你可以设计一个CGI程序根据不同的参数及用户定义的一些细节来实 时创建相应的图像(如图表)并显示。这种程序的复杂性在于图像的生成而不在于将图像输出给浏览器。幸运的是,有一些库提供了这样的接口,如Thomas boutell的gd图像库,这是生成GIF图像的一个出色工具,可以从http://www.boutell.com/gd来下载。它是用C语言写的,但是有Perl接口库gd.pm,还有基于gd支持多种语言的接口库tgd和fly。详细情况见上述gd主页。
下面是一个简单的用位图动态生成图像的计数器程序,对理解上述描述应该会有所帮助(源代码下载)
您是第 位访问本网页的人。
附:GIF的说明
现在流行着一种趋势,即远离GIF格式而采用Portable Network Graphic格式(PNG),这种变化出于技术和法律两个方面的考虑。1995年1月1日,Unisys声称他们有权要求使用LZW压缩算法的软件公司要经过他们的许可或付给他们报酬,因为他们拥有专利使用权。而GIF格式正是使用这种算法。所以,你所编写的任何用于商业应用程序中的GIF图像(包括以 CGI/WWW为基础的那些文件),都必须购买许可证或支付费用。许多软件包不再压缩GIF文件(这样会使图像变得很大),或者把GIF文件及其支持软件全部去掉。PNG使用非专利的压缩算法,从而避免了这些麻烦。在技术方面,PNG提供了较好的压缩算法(无损失,像GIF一样,但不像JPEG。JPEG 在压缩时会丢失数据)、二维交互以及24位和48位真彩支持。现在,很少有浏览器支持内插的PNG图像,但不久的将来,这种情况很可能会改变。GD图形库 文件说明了PNG支持即将来临。
第三章 计数器的编写方法(未定稿)
一、记录(log)文件
1、grep
2、page-stats
3、wusage
二、创建自己的计数器
1、使用DBM文件
2、文本文件
3、文件锁定
4、输出计数结果
5、www Homepage Access Counter
6、使用GD图形库
计数器(Access Counter)可以记录网页被访问的次数,在万维网上的使用十分普遍,其编写方法很多,从简单的SSI命令到用CGI程序生成内嵌图像等。计数器除了记录点击次数外,还可以记录访问者的IP、OS、浏览器类型等内容,使你对自己网站的访问情况有个全面的了解,本章主要介绍点击次数的统计和显示方法。
一、记录(log)文件
1、grep
对于Web服务器而言,都有记录文件记录着详细的访问信息,其名称通常为access_log,下面是一个例子:
01: dialup-9.austin.io.com - - [02/Oct/1995:20:18:05 -0500] "GET /phoenix/ HTTP/1.0" 200 2330
02: crossnet.org - - [08/Oct/1995:19:56:45 -0500] "HEAD / HTTP/1.0" 200 0
03: dialup-2.austin.io.com - - [09/Oct/1995:07:54:56 -0500] "GET /leading-rein/orders HTTP/1.0" 401 -
04: onramp1-9.onr.com - - [10/Oct/1995:11:11:40 -0500] "GET / HTTP/1.0" 200 1529
05: onramp1-9.onr.com - - [10/Oct/1995:11:11:43 -0500] "GET /accn.jpg HTTP/1.0" 200 20342
06: onramp1-9.onr.com - - [10/Oct/1995:11:11:46 -0500] "GET /home.gif HTTP/1.0" 200 1331
07: dialup-3.austin.io.com - - [12/Oct/1995:08:04:27 -0500] "GET /cgi-bin/env.cgi?
08: SavedName=+&First+Name=Eric&Last+Name=Herrmann&Street=&City=&State=&
09: zip=&Phone+Number=%28999%29+999-9999+&Email+Address=&
10: simple=+Submit+Registration+ HTTP/1.0" 200 1261
11: dialup-20.austin.io.com - - [14/Oct/1995:16:40:04 -0500] "GET /leading-rein/index.cgi?unique_id=9658-199.170.89.58-813706781 HTTP/1.0" 200 1109
注;当主页在srm.conf中被命名为welcome.html、index.cgi、index.shtml等时,对其的访问记录,可能只含有目录名而不包含该文件名。
我们可以用UNIX命令grep来统计主页被访问的次数,grep命令通常输出每一行匹配结果,但可以加上参数-c以输出匹配行的数目,grep详见UNIX帮助。下面是一个简单的例子grep.cgi:
1: #!/usr/local/bin/Perl
2: print "content-type: text/html\n\n";
3: $num = `grep -c 'GET / HTTP' /your-server-root/logs/access_log` ;
4: $num += `grep -c 'GET /index.shtml' /your-server-root/logs/access_log` ;
5: $num += `grep -c 'GET /index.html' /your-server-root /logs/access_log` ;
6: print "$num\n";
现在就可以在主页中加上SSI指令来显示计数了,例如:
01:
02: grep test
03:
04:
05: This page has been accessed
06: times.
07:
08:
09:
别忘了把此文件扩展名改为.shtml。在grep.cgi中,grep命令中包围模式的单引号告诉UNIX shell不改变该串的内容以精确匹配。这种方法有许多缺陷,首先是效率低,用grep来匹配花时间较长,可能要几秒钟的时间,这对一个简单的文本计数器而言太长了。其次,对每一个需要计数器的 页面CGI文件均不相同。最后一个对某些人来说不算是个问题,就是要把Web服务器设置成允许SSI执行,即将其目录映射略加修改。
2、page-stats
有一个叫page-stats的程序较好地解决了grep的问题。它查看HTTP daemon的access_log并寻找在标识文件中指定网页的访问,然后计算其数目并生成一个HTML形式的统计页面。这样,你既得到了页面的详细统 计信息,同时又得到了可显示的结果页面,这样的例子可在http://www.sci.kun.nl/thalia/page-stats/page-stats_sci.html找到。还可以用grep命令在统计页面中查找所需信息并生成自己的显示形式,这样速度就快多了。
注意不应在建立自己的统计时运行该程序,否则会导致冲突。应该把它放到任务列表中用UNIX命令cron定时执行,每天、每小时甚至每几分钟运行一次。cron详见UNIX帮助。
3、wusage
另外一个广为应用的服务器统计程序是由Thomas Boutell(boutell@boutell.com)编写的应用于整个服务器的wusage,它生成很详细的信息,包括服务器怎样、何时及从何处被访问等等。它每周运行一次,可以生成漂亮的图表结果,十分直观。
使用wrsage要求使用ncSA或CERN的Web Server或任何有标准记录文件格式的服务器,还需要有C编译器,wusage可在www.boutell.com得到。随着时间推移,access_file会越来越庞大,必须定期截留,这时先查看最近一周wusage是否已生成了完整的报表,确定统计结束时间,然后把access_log中该时间前的访问记录删掉,并把wusage生成的结果保存在一个目录中,以便wusage可以生成过去访问情况的图表。
二、创建自己的计数器
除了使用access_log记录文件外,我们可以创建自己的计数器。这时首先必须决定用何种形式存贮计数结果,是用文本文件还是用DBM文件,然后要决定是否进行文件同步访问的保护,这是用文件锁定来实现的,最后就是确定数据的存贮格式了。
1、使用DBM文件
对DBM文件而言,常用的函数有dbmopen()、dbmclose()、reset()、each()、values()和keys(),用于计数器时,主要使用前两个函数。dbmopen()函数把DBM文件与关联数组绑定,调用语法为:
dbmopen (%array_name, DB_filename, Read_write_mode);
如果这时指定的数据库文件不存在,则自动创建两个名为DB_filename.dir和DB_filename.pag的文件,除非把读写模式设为undef值。
缺省的,只有64个记录被读进内存,可以通过给%array_name分配大小来改变此缺省值。如果你只是给自己的网页做计数,缺省值已经足够了,但如果是给整个服务器建立计数器,一般需要更大的值。
现在看看这三个参数。当调用dbmopen时,%array_name原有的值都被清除(如果有的话),用DBM文件中的值替换掉,给之赋予新值 很简单:$array_name{'new_key'} = value; 当调用dbmclose (%array_name);语句时绑定被解除,关联数组中的内容被写如DBM文件,也可以不关闭文件而将内容写入,方法是调用reset (%array_name);语句,注意此语句并不是重置DBM文件,而是将内存中的数据写入文件。第二个参数DB_filename是不包含扩展名的, 至于读写模式详见本教程的语言部分。
下面是个使用DBM文件的计数器的简单例子:
1: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
2: if(!(defined($counters{'my_counter'})){
3: $counters{'my_counter'}=0;}
4: $counters{'my_counter'})++;
5: $count=$counters{'my_counter'};
6: dbmclose (counters);
2、文本文件
如果不用DBM文件而用文本文件,除了打开、关闭文件外,还要涉及到数据的读写问题,必须确定合适的数据格式,基本步骤如下:
1)打开文件
2)读取计数
3)自增
4)写入新值
5)关闭文件
3、文件锁定
当更新文件内容时,该文件可能同时被另一个进程修改。对计数器程序而言,如果两个或多个人同时访问页面调用了计数器程序,就会出现多个进程同时修 改同一文件的情况,这样有的进程的修改就会失效。当然这并不是太大的问题,只是失去一些计数而已,不过计数器就不准确了,访问的人越多,这个问题就越大。解决办法就是修改时通知其它试图打开该文件的进程等待,或叫文件锁定,修改完再释放,允许其它进程打开文件并修改。有两种方法,一是创建自己的锁定机制, 一种是使用系统函数flock()。
1)创建自己的文件锁
这种方法具体实现是创建和删除一个特定名称的文件,这在资源共享机制中通常称作semaphore。下面是个例子:
01: While(-f counter.lock){
02: select(undef,undef,undef,0.1);}
03: open(LOCKFILE,">counter.lock);
04: dbmopen(%COUNTERS, $DOCUMENT_ROOT/DBM_FILES/counters,0666);
05: if(!(defined($counters{'my_counter'})){
06: $counters{'my_counter'}=0;}
07: $counters{'my_counter'})++;
08: $count=$counters{'my_counter'};
09: dbmclose (counters);
10: close(LOCKFILE);
11: unlink(counter.lock);
首先检查锁定标志文件是否存在,如果存在,就说明另一个进程正在使用该文件,于是等待直到该文件(此处命名为counter.lock)不存在为 止。此处用select()的特殊形式循环等待,此语句使程序进入休眠状态一段时间,该时间段由最后一个参数定义。之所以不用sleep()函数是因为其 基本单位为秒,对这种文件锁定而言太长了,几个微秒就足够了。
当锁定标志文件不再存在,就创建自己的锁定标志文件并开始修改计数,完成后关闭该文件并用unlink函数将之删除,这样其它的进程又被允许修改计数。锁定标志文件并不是特殊的文件,其文件名也可以由你自己随意选择。
2)使用flock()
其实锁定文件是很普通的编程步骤,系统函数flock()提供了这一功能,如果在你的系统上不提供的话,可以使用前面介绍的方法自己实现。
flock()的语法为:
flock (filehandle, lock_type);
参数filehandle为用open()函数打开的文件句柄,lock_type可以为下面四个值之一:
1:定义共享锁。对计数器而言不适用。
2:定义排他锁。
3:定义非阻止锁。此处亦不用。
4:解除锁定。
使用flock()实现的文件锁定例子如下:
1a: dbmopen(%counters,"filename", 0666);
or
1b: OPEN(counters,"<filename")'
2: flock(counters,2);
3: if(!(defined($counters{'my_counter'})){
4: $counters{'my_counter'}=0;}
5: $counters{'my_counter'})++;
6: $count=$counters{'my_counter'};
7: dbmclose (counters);
8: flock(counters,8);
4、输出计数结果
现在一切就绪,只剩下输出我们的计数结果了,有三种输出方法:
1)用上面谈到的SSI方法输出。
2)创建各种文本格式输出。
3)生成各种漂亮的图形结果输出,本教程的《动态创建图像》一章讲述了基本原理并提供了一个x-bitmap格式的小例子,下面介绍两个更完善和漂亮的程序/库,这两个例子均需要C编译器。
5、www Homepage Access Counter
这是一个广为应用的网页计数程序,它利用已有的GIF图象连接起来生成一个GIF图象,此程序是用C语言写的,有适用于各种操作系统的版本,可以在www.fccc.edu/users/muquit/Count.html下载。它提供了很多参数,功能比较齐全,生成的图象结果也很漂亮,可以选择图像格式,其自带了一些数字样式,但你可以增加自己的数字图像生成各种想要的图像,cervantes.comptons.com/digits/digits.htm提供了很多GIF数字图象。其参数通过QUERY_STRING传递,且必须是小写字母,下面是个较复杂的调用例子:
其参数详细说明和使用方法详见上述下载网址。如果有必要的话,研究并修改一下其源程序可以使你生成更适合于自己需要的图象。
6、使用GD图形库
Counter利用现有的数字图象简化了一部分的工作,其目的就是用于图形计数器。GD图形库的功能更加强大,不仅可以用于创建图形计数器,还可以生成各 种统计图表,还提供了Perl接口库。GD及其衍生的程序详见本教程《动态创建图像》一章。
在下载的程序中有一个名为gddemo.c的程序演示了其使用方法,在sparke.cs.nyu.edu:8086/cgi.htm有其用于计数器的例子。下面是一个通过GD.pm调用GD图形库生成图像的Perl程序例:
use GD;
# create a new image
$im = new GD:Image(100,100);
# allocate some colors
$white = $im->colorAllocate(255,255,255);
$black = $im->colorAllocate(0,0,0);
$red = $im->colorAllocate(255,0,0);
$blue = $im->colorAllocate(0,0,255);
# make the background transparent and interlaced
$im->transparent($white);
$im->interlaced('true');
# Put a black frame around the picture
$im->rectangle(0,0,99,99,$black);
# Draw a blue oval
$im->arc(50,50,95,75,0,360,$blue);
# And fill it with red
$im->fill(50,50,$red);
# Convert the image to GIF and print it on standard output
print $im->gif;