文件测试
1. 文件测试操作符
perl提供了一组用于测试文件的操作符,借此可以返回特定的文件信息。
- 通常使用 -x的形式调用,其中x值得时特定的测试操作,如-w -r等。
- 通过特定的文件测试符,可以让我们的程序变得更加智能。
- 使用格式: -r $filename (连字符+测试场景 文件名称或文件句柄)
- 默认检查$_,但是-t 的默认值是STDIN
#!/usr/bin/perl
#使用-e判断文件是否存在
#如果文件存在,调用die函数结束程序,这样可以保证已存在的文件不会被覆盖
die "Oops! A file call '$filename' already exists.\n"
if -e $filename;
#测试某个文件是否保持更新
#这里的-M返回的是文件最后一次修改时间到让当前程序启动时刻之间的天数
warn "Config file is looking pretty old.\n"
if -M CONFIG > 20;
#如果我们希望查找文件大于100k,并且超过90天未访问的文件
my @original_files = qw/fred betty barney wilma pebbles dino/;
my @big_old_files;
foreach my $filename (@original_files) {
push @big_old_files,$filename
if -s $filename > 100_000 and -A $filename > 90; # -s 返回的是以字节计算的文件大小
}
表1.1 文件测试操作符及其意义
文件测试符 | 意义 |
---|---|
-r | 文件或目录,对目前(有效的)用户或组来说是可读的 |
-w | 文件或目录,对目前(有效的)用户或组来说是可写的 |
-x | 文件或目录,对目前(有效的)用户或组来说是可执行的 |
-o | 文件或目录,由目前(有效的)用户拥有 |
-R | 文件或目录,对实际的用户或组来说是可读的 |
-W | 文件或目录,对实际的用户或组来说是可写的 |
-X | 文件或目录,对实际的用户或组来说是可执行的 |
-O | 文件或目录,由实际的用户拥有 |
-e | 文件或目录,是存在的 |
-z | 文件或目录存在而且没有内容(对目录来说永远是假) |
-s | 文件或目录存在并且有内容(返回值是以字节为单位的文件大小) |
-f | 是否为普通文件 |
-d | 是否为目录文件 |
-l | 是否为符号链接 |
-S | 是否为socket类型的文件 |
-p | 是否为命名管道,即FIFO队列 |
-b | 是否为块设备(比如某个挂载的网盘) |
-c | 是否为字符设备文件(比如某个IO设备) |
-u | 文件和目录设置了setuid位 |
-g | 文件和目录设置了setgid位 |
-k | 文件和目录设置了setcky位 |
-t | 文件句柄是TTY设备 |
-T | 看起来像是文本文件 |
-B | 看起来像是二进制文件 |
-M | 最后一次修改至今的天数 |
-A | 最后一次访问至今的天数 |
-C | 最有一次文件节点编号变更至今的天数 |
注:
- 如无特殊声明,返回的都是布尔真/假值;
- -r -w -x -o需要查看用户ID,看他们是否具有相应的文件权限;
- 有效用户:负责运行这个程序的人。
1.1 同一文件的多个属性测试
如果要一次性测试一个文件的多个属性,可以将个文件的测试组成一个逻辑表达式。
#!/usr/bin/perl
#检查可读可写的文件
if (-r $filename and -w $filename) {
... }
#使用虚拟文件句柄_ (是的就是下划线字符)
if (-r $filename and -w _) { # _告诉perl使用上次查询过的文件信息做测试
... }
# _ 指的是调用 _ 之前最后一次查询的文件信息
if (-r $filename) {
...}
&lookup ($other_filename);
if(-w _){ #此时查询的是$other_filename是否具有写权限
...}
sub lookup{
return -w $_[0];
}
1.2 栈式文件测试操作符
perl5.10之后允许使用栈式文件测试操作符。
- 格式: 将文件测试符排成一列,放在被测试的文件名前
#!/usr/bin/perl
#续上例
if (-w -r $filename){ #注意:这里先检查 -r 再检查 -w,顺序由右向左依次执行
...}
#也可以使用更多的文件测试操作符
if (-w -r -o -x -d $filename){ #顺序由右向左依次执行
...}
#对于需要返回其他值的文件测试符而言,栈式文件操作符反而鸡肋
if (-s -d $filename < 512) { #错误!
}
if ((-d $filename and -s _) < 512) { #以上程序和此句一致,当-d为假,永远小于512,if判断条件为真
}
if (-d $filename and -s _ < 512){ #此时建议使用这种写法,因为-s是返回以字节计算的文件大小
...}
2. stat 和 lstat函数
使用文件测试操作符可以查看文件的许多属性,但是还有许多其他属性信息没有对应的文件测试操作符。比如说,没有任何文件测试符会返回文件链接数或者文件拥有者的ID(uid),此时可以使用stat函数,和Unix中的stat类似,返回丰富的文件信息。
-
stat函数:
- 格式:stat($filename)
- 参数允许格式:文件句柄,虚拟文件句柄_,返回文件名的表达式
- 如果执行失败(无效文件名,文件不存在等),函数返回空列表
- 如果执行成功,返回一个包含13个数字元素的列表。
- my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = state($filename)
-
表2.1 stat函数的返回值
序号 | 参数 | 含义 |
---|---|---|
1 | $dev | 文件所在设备的编号 |
2 | $ino | 文件的 inode 编号 |
3 | $mode | 文件的权限位集合( -rwxr-xr-- 这种) |
4 | $nlink | 文件或者目录的硬链接数(不包括软连接) (目录一般大于2,文件一般等于1) |
5 | $uid | 文件所有者的用户ID |
6 | $gid | 文件所有者所在组的ID |
7 | $size | 以字节为单位的文件大小和 文件测试操作符 -s 返回结果一致 |
8 | $atime | 三种不同纪元(epoch) 算起的秒数的时间戳之一 |
9 | $mtime | 三种不同纪元(epoch) 算起的秒数的时间戳之一 |
10 | $ctime | 三种不同纪元(epoch) 算起的秒数的时间戳之一 |
- 注意:
- 如果stat的参数是符号链接,那么stat返回文件属性并非符号链接本身,而是其指向的对象;
- 如果需要查询符号链接的文件属性,可以使用 lstat 函数,如果 lstat 的参数不是符号链接,返回空列表
- stat 和 lstat 函数的默认操作数是 $_
- localtime函数
从state返回的时间值格式可能是1231253675416类似的形式,这样的数字可读性较差,所以我们希望使用比较容易阅读的形式,如: Sat Jan 20 00:54:09 2018这种,Perl中可以在标量上下文中实现这种转换:
#!/usr/bin/perl
my $timestamp = 1234432456;
my $date = localtime $timestamp;
#不提供参数时,默认使用当前time返回的时间值
my $time = localtime;
#类似的还有gmtime,返回世界标准时间,格林威治标准时间
my $time = gmtime;
- localtime的返回列表:
- my = ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
表2.2 local函数的部分返回值
参数 | 意义 |
---|---|
$mon | 返回0~11用于索引月份名 |
$year | 返回从1900年算起的年数,+1900就是现在的年数 |
$wday | 返回0~6用于索引星期名 |
$yday | 返回今年的第几天,范围是0~365 |
3. 位运算操作符
位运算符对数据执行二进制数学运算。
- 表3.1 位运算操作符及其意义
表达式 | 意义 (10 = 1010 12 = 1100) |
---|---|
10 & 12 | 按位与,结果为1000 -> 8 |
10 | 12 | 按位或,结果为1110 -> 14 |
10 ^ 12 | 按位异或,结果为0110 -> 6 |
10 << 2 | 按位左移,结果为101000 -> 40,低位补零 |
12 >> 2 | 按位右移,结果为11 -> 3,丢弃移除位 |
~10 | 按位取反,结果为0101 -> 5 |
3.1 使用位字符串
所有的位运算符既可以操作位字符串,也可以操作整数;
- 如果两个操作数都是整数,那么结果也是整数,如10 & 12 = 8
- 如果两个操作数都是字符串,那么结果也是字符串,如"\xAA" | “\x55” = “\xFF”
在perl5.22之前,只要操作数中某一个是数字类型,那么就会按照数字的位运算方式执行;
#!/usr/bin/perl
my $number = 137;
my $number_str = '137';
my $string = 'Amelia';
say "number_str & string",$number_str & $string; #打印:number_str & string ?!%
say "number & string ",$number & $string; #打印:number & string 0
say "number & number_str",$number & $number_str; #打印:number & number_str 137
say "number_str & string",$number_str & $string; #打印:number_str & string 0
#Lin1: 字符串运算得到的结果
#Lin2:因为number是数字,因此先把Amelia转换为数字,然后按照数字方式进行位运算得到0
#Line3:因为number是数字,因此先把‘137’转换为数字,然后按照数字方式进行位运算得到137
#Line4:perl上次处理string和number_str的时候将其修改了他们为数字类型,最后发现两个数字类型进行比较得到0
- 注:
- 其实Perl中有双值变量的概念(dualvar),标量可以具有两个版本的值,数字+字符串;
- 大部分时候不会出现问题,反而更加方便;
- 如 $! 的字符串版本是返回错误消息,而数字版本是返回错误代号。
Perl5.22以后增加了实验性的新特性已解决类似的奇怪问题,在使用操作符进行是运算时,我们最好能够确定以何种方式进行计算,不管操作数之前的操作历史。在编译时使用bitwise属性即可。
#!/usr/bin/perl
use feature qw(bitwise);
no warning qw(experimental::bitwise);
#全部当做数字进行计算
my $number = 137;
my $number_str = '137';
my $string = 'Amelia';
say "number_str & string",$number_str & $string; #打印:number_str & string 0
say "number & string ",$number & $string; #打印:number & string 0
say "number & number_str",$number & $number_str; #打印:number & number_str 137
say "number_str & string",$number_str & $string; #打印:number_str & string 0
#在操作符后加一个.字符,进行表示以字符串方式进行位运算,如 &.
say "number_str & string",$number_str &. $string; #打印:number_str & string ?!%
say "number & string ",$number &. $string; #打印:number & string ?!%
say "number & number_str",$number &. $number_str; #打印:number & number_str 137
say "number_str & string",$number_str &. $string; #打印:number_str & string ?!%