1. 同一文件的多项属性测试
如果要一次测试某个文件的若干属性,可以将各个文件测试组成一个逻辑表达式
if (-r $filename and -w $filename) {
...
}
这是个很耗费资源的操作,每次进行文件测试时,perl都得从文件系统中取出所有相关信息(实际上,每次perl都在内部做了一次stat操作)。
虽然在做-r测试的时候,我们已经拿到了所有相关信息,可到了做-w测试的时候,perl又要去取一遍相同的信息
perl有个特别的简写可以避免这些重复劳动,它就是虚拟句柄_
,它会告诉perl用上次查询过的文件信息来做测试,现在perl只需要查询一次文件信息即可
if(-r $filename and -w _){
...
}
不是只能在一条语句中连续使用_
if(-r $filename){
print "The file is readable!\n";
}
if(-w _){
print "The file is writable!\n";
}
这么用的时候,必须要清楚代码最后一次查询的是否为同一个文件。如果在两个文件测试之间又调用了某个子程序,那么最后一个查询的文件可能会变化
if(-r $filename){
print "The file is readable!\n";
}
lookup($other_filename);
if(-w _){
print "The file is writable!\n";
}
sub lookup{
return -w $_[0];
}
2. 栈式文件测试操作符
在perl5.10之前,如果要一次测试多个文件属性,只能分开为若干独立的操作,就算是使用了虚拟句柄_也是这样
比如说,我们想测试某个文件是否可读写,就必须分别做可读测试和可写测试
if(-r $filename and -w _){
print "The file is both readable and writable!\n";
}
如果能一次完成两个测试就好了!从perl5.10开始,我们可以使用“栈式(stack)”写法将文件测试操作符排成一列,放在要测试的文件名前
use v5.10;
if(-w -r $filename){
print "The file is both readable and writable!\n";
}
在使用栈式写法是,靠近文件名的测试会先执行,次序为从右往左,不过一般来说,测试次序不是很重要
在一些复杂情况下,这种栈式文件测试特别好用
use v5.10;
if(-r -w -x -o -d $filename){
print "My directory is readable, writable, and executable!\n";
}
如果你需要的是返回非布尔值的测试,栈式写法很垃圾
use v5.10;
if(-s -d $filename < 512){ # 本来想确认某个小于512字节的目录。这样是错误的!不要这样做!
say 'The directory is less than 512 bytes!';
}
当-d返回假时,perl将假值同数字512作比较。比较的结果就变成真,因为假等效为数字0,而0永远小于512
if(-d $filename and -s _ < 512){
print "The directory is less than 512 bytes!\n";
}
3. stat和lstat函数
之前说的文件测试符可以获取某个文件或文件句柄的各种常用属性,不过还有很多属性信息没有对应的文件测试操作符,如果想知道文件所有其它相关信息,可以用stat函数
stat函数的参数可以是文件句柄(包括虚拟句柄_),或是某个会返回文件名的表达式。如果stat函数执行失败(常常是因为无效的文件名或者文件不存在),它会返回空列表,要不然返回一个含13个数字元素的列表
my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev,
$size, $atime, $mtime, $ctime, $blksize, $blocks)
= stat($filename);
$dev
和$ino
即文件所在设备的编号与文件的inode编号,这2个编号决定了文件的唯一性。即使文件具有多个不同的文件名(使用硬链接创建),设备编号和inode编号的组合依然是独一无二的
$mode
文件的权限位集合还包含其它信息位
$nlink
文件或目录的(硬)链接数,也就是这个条目有多少个真实名称。这个数值对目录来说总会是2或更大的数字,对文件来说则(通常)是1
$uid
和$gid
表示文件拥有者的用户ID及组ID的数字
$size
以字节为单位的文件大小
$atime
,$mtime
和$ctime
3种表示从纪元(epoch)算起的秒数的时间戳。在unix与某些其它系统中,纪元从公元1970年世界标准时间的午夜算起
对符号链接名调用stat函数,会返回符号链接指向对象的信息,而非符号链接本身的信息(出发该链接指向的对象目前无法访问)。如果需要的是符号链接本身的信息(一般没用),可以用lstat函数
模块File::stat提供了兼容stat且更为友好的操作界面
4. localtime函数
获得的时间戳值(比如从stat函数返回的时间戳)看起来通常像12535752655这样的形式,这个数字实在是不方便,除非想通过剑法来比较两个时间戳大小。
perl可以在标量上下文中使用localtime函数将时间戳值转换成比较容易阅读的形式
my $timestamp = 1454133253;
my $date = localtime $timestamp;
在列表上下文中,localtime函数会返回一个数字元素组成的列表
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
$mon
: 范围从0到11的月份值,可以用来索引月份名
$year
: 从1900年开始算的年份,这个值+1900就是实际的年份
$wday
: 从0(星期天)到6(星期六)
$yday
: 表示目前是今年的第几天,从0(1月1日)到364/365(12月31日)
gmtime函数和localtime函数一样,不过gmtime函数返回的是世界标准时间(格林威治时间)
可以用time函数来从系统时钟取得当前的时间戳
不提供参数的情况下,不论localtime或gmtime函数,默认情况下都使用当前time返回的时间值
my $now = gmtime; # 取得当前世界标准时间的时间戳字符串
5. 位运算操作符
如果需要逐位进行运算,比如对stat函数返回的权限位进行处理,就必须用到位运算操作符
位运算操作符对数据执行二进制数学运算
表达式 | 意义 |
---|---|
10 & 12 | 按位与,哪些位在两边同时为真(8) |
10 | 12 |
10 ^ 12 | 按位异或,哪些位在任何一边为真,但另一边为假(6) |
6 << 2 | 按位左移,将左边操作数向左移动数位,移动位数由右边操作数指定,并以0来填补最低位(24) |
25 >> 2 | 按位右移,将左边操作数向右移动数位,移动位数由右边操作数指定,并丢弃移出的最低位(6) |
~ 10 | 按位取反,也称为取反码,返回操作数逐位反向后的数值(0xFFFFFFF5) |
位操作的结果可以给chmod使用
# $mode是从配置文件CONFIG的stat信息中取出的状态值
warn "Hey, the configuration file is world-writable!\n"
if $mode & 0002; # 配置文件有安全隐患
my $classical_mode = 0777 & $mode; # 遮蔽额外的高位
my $u_plus_x = $classical_mode | 0100; # 将一个位设为1
my $go_minus_r = $classical_mode & (~ 0044); # 将两个位都设为0
6. 使用位字符串
所有这些位运算操作符既可以操作位字符串(bitstring),也可以对整数进行操作。如果操作数都是整数,结果也会是整数(整数至少会是一个32位整数,但如果你的硬件支持更多位的话,就会更大。例如在64位的机器上,~10的结果会是0xFFFFFFFFFFFFFF5,而不是32机器上的0xFFFFFFF5)
如果位运算操作符的2个操作数都是字符串的话,perl会把它当成位字符串来处理。也就是说"\xAA"|"\x55"的结果会是"\xFF"。需要注意的是,这个例子里的值都是单字节的字符串,其结果是8个比特位上都是1的字节。perl对位字符串的长度没有限制
这是少数perl区分字符串和数字的地方
只要perl判断其中一个操作数是数字类型,那就会按照数字的位运算方式执行
use v5.10;
my $number = 137;
my $number_str = '137';
my $string = 'Amelia';
say "number_str & string: ", $number_str & $string; # 按照字符串方式执行的位运算
say "number & string: ", $number & $string; # 其中一个操作数是数字,perl把另一个操作数先转换成数字,结果是0
say "number & numbers_string: ", $number & $number_str; # 将'137'转换为数字137,和另一个操作数完全相同,因为每个比特位上的内容都相同,所以最后的运算结果还是原来的数字137
say "number_str & string: ", $number_str & $string; # 这个结果居然是0!!!
当我们重新运行第一行语句时,结果却发生了变化!虽然我们没有手动修改变量值,但在前2条语句perl相继修改了$number_str
和$string
的值为数字类型。在perl做这件事的背后,其实默默保存了转换结果以备重复实验。当执行最后一行时,perl查看这2个变量是否已有转好的结果,一看都是数字,于是按照两边都是数字的方式执行位运算
其实perl有一个双值变量(dualvar)的概念。标量可以同时有2个版本的值,一个数字类型的,一个字符串类型的。大部分时候这没啥问题,某些情况下反而更方便。比如系统错误变量$!
的字符串版本时错误信息描述,数字版本是错误代号。可以参考Scalar::Util模块的说明
自perl5.22开始增加了实验性的新特性以解决类似的奇怪问题。在使用操作符进行计算时,我们最好能明确操作数以何种方式参与计算,不管操作数之前曾有过什么历史。如果要执行数字位运算启用bitwise特性会令位操作符将操作数统一按照数字类型计算
use v5.22.0;
use feature qw(bitwise);
no warnings qw(experimental::bitwise);
my $number = 137;
my $number_str = '137';
my $string = 'Amelia';
say "number_str & string: ", $number_str & $string;
say "number & string: ", $number & $string;
say "number & number_str: ", $number & $number_str;
say "number_str & string: ", $number_str & $string;
这一次输出的第一行不再是天书了,即使2边的操作数都是字符串,但perl将它们都当数字,最后计算结果是0
number_str & string: 0
number & string: 0
number & number_str: 137
number_str & string: 0
如果要以字符串方式进行位运算,bitwise特性增加了一个新的位操作符写法,后补一个.表示
use v5.22.0;
use feature qw(bitwise);
no warnings qw(experimental::bitwise);
my $number = 137;
my $number_str = '137';
my $string = 'Amelia';
say "number_str &. string: ", $number_str &. $string;
say "number &. string: ", $number &. $string;
say "number &. number_str: ", $number &. $number_str;
say "number_str &. string: ", $number_str &. $string;