perl文件测试操作符
2013-06-10 22:23:27

在shell中通过test命令或者中括号[]可以进行文件测试以及其它类型的测试,例如判断文件是否存在,比较操作是否为真等等。perl作为更强大的文本处理语言,它也有文件测试类表达式,而且和shell的文件测试用的字母符号都类似。perl中测试文件的属性来源于其内置函数stat,它可以获得文件的13项属性。文件测试操作符组成自连字符和某个字母,后面接着所要测试的文件名或句柄。大部分的文件测试符会返回真或假值,或一些结果。

-X FILEHANDLE
-X EXPR
-X DIRHANDLE
-X

A file test, where X is one of the letters. This unary operator takes one argument, either a filename, a filehandle, or a dirhandle, and tests the associated file to see if something is true about it. If the argument is omitted, tests $_ , except for -t , which tests STDIN. Unless otherwise documented, it returns 1 for true and '' for false, or the undefined value if the file doesn't exist. Despite the funny names, precedence is the same as any other named unary operator.

基本格式:文件测试操作符 + 文件名/文件句柄

选项
-r:文件可以被有效的UID/GID读取
-w:文件可以被有效的UID/GID写入
-x:文件可以被有效的UID/GID执行
-o:文件被有效UID所有
-R:文件或目录可以被真实的UID/GID读取
-W:文件或目录可以被真实的UID/GID写入
-X:文件或目录可以被真实的UID/GID执行
-o:文件被有效UID所有
-O:文件被真实的UID所有
-e:文件或目录名存在
-z:文件存在,大小为0(目录恒为false),即是否为空文件,
-s:文件或目录存在且不为0(返回字节数)  
-f:为普通文件
-d:为目录
-l:为符号链接
-p:文件是命名管道(FIFO)
-S:为socket(套接字)
-b:为block-special (特殊块)文件(如挂载磁盘)
-c:为character-special (特殊字符)文件(如I/O 设备)
-u 文件或目录具有setuid属性
-g 文件或目录具有setgid属性
-k:文件或目录设置了sticky位
-t:文件句柄为TTY(系统函数isatty()的返回结果;不能对文件名使用这个测试)
-T:是否为文本文件
-b:文件是否为特殊块文件
-B:是否为二进制文件
-M:文件上一次被修改的时间(单位:天)
-A:文件上一次被访问的时间(单位:天)
-C:文件的(inode)索引节点修改时间(单位:天)

文件测试操作符如下表所示:

操作符描述
-A文件上一次被访问的时间(单位:天)
-B是否为二进制文件
-C文件的(inode)索引节点修改时间(单位:天)
-M文件上一次被修改的时间(单位:天)
-O文件被真实的UID所有
-R文件或目录可以被真实的UID/GID读取
-S为socket(套接字)
-T是否为文本文件
-W文件或目录可以被真实的UID/GID写入
-X文件或目录可以被真实的UID/GID执行
-b为block-special (特殊块)文件(如挂载磁盘)
-c为character-special (特殊字符)文件(如I/O 设备)
-d为目录
-e文件或目录名存在
-f为普通文件
-g文件或目录具有setgid属性
-k文件或目录设置了sticky位
-l为符号链接
-o文件被有效UID所有
-p文件是命名管道(FIFO)
-r文件可以被有效的UID/GID读取
-s文件或目录存在且不为0(返回字节数)
-t文件句柄为TTY(系统函数isatty()的返回结果;不能对文件名使用这个测试)
-u文件或目录具有setuid属性
-w文件可以被有效的UID/GID写入
-x文件可以被有效的UID/GID执行
-z文件存在,大小为0(目录恒为false),即是否为空文件,

+文件的访问权限测试
-r, -w, -x, -o: 测试文件或目录对当前有效的用户或组(即当前负责运行程序的“人”)是否可读、可写、可执行或所拥有。

-R, -W, -X, -O: 测试文件或目录对实际的用户或组是否可读、可写、可执行或所拥有。

-O和-o只测试用户标识符,不测试组标识符。

+文件类型测试
-f, -d, -l, -S, -p, -b, -c: 测试文件句柄是否为普通文件、目录、符号链接、Socket、pineline、块设备、字符设备
-T: 测试文件是否看起来像文本文件
-B: 测试文件是否看起来像二进制文件

Perl会打开文件,检查开头的几千个字节。如果有很多空字节、控制字符和设定了高位(第八位为1)的字节,那么该文件看起来像二进制文件。如果很平常,那么该文件看起来像字节文件。对于很多非ASCII码的字符集, Perl可能会猜错。
如果文件不存在,-T和-B都会返回假;如果文件为空,-T和-B都会返回真。
-t: 测试文件句柄是否为具有交互能力的tty设备,如STDIN(键盘)。普通的文件和管道没有交互的能力。

+文件存在性测试
-e, 测试文件或目录是否存在
die "Opps! A file called '$filename' already exists.\n" if -e $filename;
-z, 测试文件是否存在并且没有内容,对目录来说永远为假
-s, 测试文件或目录是否存在,并返回文件大小(以字节为单位)

+文件时间戳测试
-M, -A, -C测试文件最后被修改、最后被访问、inode被改变(有的系统指文件创建)到现在的天数,这个时间值为浮点数。
检查时间的原点为程序开始运行的时刻,这取决于变量$^T的值,可以修改这个变量。
如果某个天数为负数,说明那个程序已经运行了一段时间,才找到某个刚刚才被访问到的文件,或者这个文件的时间戳被设置成了未来时间。

+文件其它属性测试

-u, -g, -k测试文件或目录是否设置了setuid、setgid、sticky位
如果文件测试操作符后面没有写文件名或文件句柄,默认参数是$_里的文件名称。
foreach (@lots_of_filenames) {
print "$_ is a readable\n" if -r;
my $size_in_KB= (-s) / 1024;
#-s必须加上(),否则会把/ 1024也当成参数的一部分
...
}

借助于第三方的模块或函数,进行更多的处理。
+stat和lstat函数

通过stat函数来获取文件的详细信息。
参数:文件名或文件句柄,默认参数:$_
返回值:含有13个元素的列表或空列表(此时函数执行失败),若底层系统调用失败,则返回undef。
my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks)=stat($filename);

$dev和$ino: 文件的设备编号和inode编号,其组合独一无二。
$mode: 文件的权限位组合,用八进制数字表示
$nlink:文件或目录的(硬)链接数目
$uid, $gid: 不说了
$size: 同-s的返回值
$atime, $mtime, $ctime: 三种时间戳,以系统的时间格式表示,32位整数,表示从纪元(Epoch,计量系统时间的基准点)开始计算的秒数。在Unix系统中,纪元为1970年世界标准时间的午夜。怎样把时间戳的值转换成我们常用的时间格式?

对符号链接的名称使用stat函数,返回的是该链接所指对象的信息。若要返回符号链接本身的信息,使用lstat函数。如果lstat的参数不是符号链接,它会返回同stat一样的信息。

+localtime函数
标量上下文
my $timestamp=1360630098;
my $date=localtime $timestamp;
#把时间戳转换为可读的时间字符串:

列表上下文
my ($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst)=localtime $timestamp;
#18,48,0,1,5,107,5,151,0
$mon从0到11,$year从1900年算起的年份, $wday从0到6,$yday从0到364(365)。
my $date=localtime; #默认情况下返回系统当前时间
my $date=gmtime; #默认情况下返回系统当前时间(世界标准时间)
my $date=time; #默认情况下返回系统当前时间的时间戳
#使用Time::Local模块中的timelocal函数,用年月日等信息获
#得对应的时间戳
use Time::Local;
my $time=timelocal($sec,$min,$hr,$day,$mon,$yr);

+下划线文件句柄
程序里每次使用stat, lstat, 或某个文件操作符时,Perl必须向系统请求该文件的stat缓冲区(这实际上是stat系统调用返回的缓冲区)。用_文件句柄做参数,Perl从内存中找出前一次文件测试,stat,lstat的参考信息,而不是向系统再次请求数据。可以减少不必要的系统调用,使程序的运行速度更快。需要注意的是,可能某个子例程内部调用过stat,将预期的缓冲区的内容覆盖。
my @original_files=qw/ fred barney betty wilma /;
my @big_old_files; #要做备份的文件列表
foreach (@original_files) {
push @big_old_files, $_
 if (-s) > 100_100 and -A _ > 90;
 #buf.st_size > 100000 && buf.st_atime > 90
 #第二个测试使用了第一个测试留下来的数据
}

注意事项
1、多目运算时
-s($file) + 1024   # probably wrong; same as -s($file + 1024)

(-s $file) + 1024  # correct
显然,第二种用法才是正确的。

2、链式操作
新版本的语法糖,从Perl 5.9.1开始, as a form of purely syntactic sugar, you can stack file test operators, in a way that -f -w -x $file is equivalent to -x $file && -w _ && -f _ .

(This is only fancy fancy: if you use the return value of -f $file as an argument to another filetest operator, no special magic will happen.)

例子:
die "Opps! A file called '$filename' already exists. "
if -e $filename;

warn "Config file is looking pretty old! "
if -M CONFIG > 28;

参考来源:
-X


文件与进程测试操作

本节总结自骏马金龙的博客空间,感谢原作者。

测试符

测试符号都是短横线开头,加一个字母。例如测试文件是否存在-e "fa.log"。在可能产生歧义的情况下,这些测试符可以用括号包围,例如:(-e "fa.log")。

注意,perl主要应用于Unix类系统,而Unix中一切皆文件,所以下表中出现的"文件"如非特地指明,否则它既可表示普通文件,也表示目录,还表示其它类型的文件。以下是文件大小测试符:
符号:意义
-------------------------------
-e:文件是否存在
-z:文件是否存在且为空(对目录而言,永远为假)
-s:文件是否存在且不为空,返回值是文件大小,单位为字节

其中-s会返回文件的字节数大小。对于真假的判断,通过这个返回值也可以直接判断,大于0表示非空,等于0表示空。例如:
print "not empty" if "-s /tmp/fa.log";
print "empty" unless "! -s /tmp/fa.log";
print ((-s "/usr/bin/passwd") + 10);    # 返回文件字节数+10

以下是文件类型测试符:
符号:意义(同时检测文件的存在性)
-------------------------------
-f:文件是否为普通文件
-d:文件是否为目录文件
-l:文件是否为软链接(字符链接)
-b:文件是否为块设备
-c:文件是否是字符设备文件
-p:文件是否为命名管道
-S:文件是否为socket文件

以下是权限类测试符:
首先区分一下effective uid和real uid:
1.real uid:文件调用者的uid
2.effective uid:文件调用最后生效的uid

如未对文件进行特殊设置,real uid和effective uid是一致的。但是如果设置了setuid属性,那么文件在执行的时候会提升为某用户的权限(一般是提升为root),这时候effective uid就是root,而real uid则是文件调用者的uid。

比如freeoa用户读取一个文件,则这个文件的real uid就是freeoa。比如/usr/bin/passwd这个文件设置了setuid,调用这个程序的用户是real uid,最后执行的时候提权为root用户,那么这个程序的effective uid就是root。一般来说,只会去检测real uid的权限属性。只有极少数文件会设置setuid/setgid/sticky,去检测这类文件的权限的机会就更小了。

符号:意义(同时检测文件的存在性)
-------------------------------
-r:文件(对effective uid)是否可读
-w:文件(对effective uid)是否可写
-x:文件(对effective uid)是否可执行
-o:文件(对effective uid)的所有者

-R:文件(对real uid)是否可读
-W:文件(对real uid)是否可写
-X:文件(对real uid)是否可执行
-O:文件(对real uid)的所有者

-u:文件是否设置了setuid (setuid只对可执行普通文件有效)
-g:文件是否设置了setgid (setgid只对普通文件或目录有效)
-k:文件是否设置了sticky (sticky属性只对目录有效)

例如:
print "readable" if -r "/tmp/fa.log";

以下是其它测试符:
符号:意义
-------------------------------
-e:文件是否存在
-z:文件是否存在且为空(对目录而言,永远为假)
-s:文件是否存在且不为空,返回值是文件大小,单位为字节

-M:最后一次修改(mtime)距离目前的天数
-A:最后一次访问(atime)距离目前的天数
-C:最后一次inode修改(ctime)距离目前的天数

-T:文件看起来像文本文件
-B:文件看起来像二进制文件
-t:文件句柄是否为TTY设备(该测试只对文件句柄有效)

上面-M/-A/-C会计算天数,它是(小时数/24)来计算的。例如6小时前修改的文件,它的天数就是0.25天。

上面的-T/-B是mime类型猜测,perl会根据文件的前几个字节来猜测这个文件,当然也不一定能猜对,但大多数时候是没什么问题的。

例如:
# 修改文件mtime时间为6小时前
touch -m -t "6 hours ago" /tmp/fb.log

如果perl程序内容为:
print (-M "/tmp/fb.log");

它的执行结果将输出0.25:
0.250162037037037

以下是文件mime类型猜测,脚本内容如下:
print "text file\n" if -T "$ARGV[0]";
print "Binary file\n" if -B "$ARGV[0]";

执行结果如下:
$ ./ft.pl 1.pl
text file

$ ./ft.pl /etc
Binary file

$ ./ft.pl initramfs-3.10.0-327.el7.x86_64.img
Binary file

$ ./ft.pl /bin/ls
Binary file

测试符的陷阱

测试符操作可以省略参数,这时它的操作对象是默认变量$_。但是对于-t测试符来说例外,它的默认操作对象是<STDIN>,因为它的对象是文件句柄,而非文件名。例如:
#!/usr/bin/perl
foreach (`ls`){
    chomp;
    print "$_ is executable\n" if -x;
}

但是省略参数的时候,很容易出错。操作符会把后面任何一个非空格字符(串)当作它的参数。例如-s返回的是字节大小,可让它按kb显示。
print (-s/1024);

但这时的"-s"会把/当作它的测试对象参数,而不是$_。所以省略参数的时候,建议将测试符用括号包围起来:
print ((-s)/1024);

测试文件多个属性符之缓存文件测试信息

如果想要同时测试文件的可读性、可写性:
print "writable and readable\n" if -w "/tmp/fa.log" and -r "/tmp/fa.log";

但是这不是最佳方式,因为perl每执行一次测试符表达式,都需要对文件执行一次stat函数,但实际上第二次测试执行的stat是多余的,因为一次测试就可以获取到文件的所有属性。perl中有一个特殊的缓存文件句柄_(就是一个下划线),它可以最近的缓存文件属性信息。下面是等价的操作:
print "writable and readable\n" if -w "/tmp/fa.log" and -r _;

_的缓存周期可以延续,直到测试下一个文件,所以将两次测试分开写也可以。但需要注意的是,在使用_的时候要确保它缓存的对象正是所需要的文件属性。

print "fa.log writable\n" if -w "/tmp/fa.log";
print "fb.log writable\n" if -w "/tmp/fb.log";
print "fb.log writable\n" if -r _;

上面第三个语句中'_'缓存的是/tmp/fb.log文件的属性。


测试文件多个属性符之栈式文件测试

可以将多个测试操作符连在一起写:
1.连写的时候,从右向左依次执行,并按照and逻辑运算符判断真假。也就是说,先测试靠近文件名的操作符;
2.对于返回真/假值的测试符,连写的测试符前后顺序不会影响结果;
3.对于返回非真/假值的测试符(即-s/-M/-A/-C),连写测试符时应尽量谨慎,最保险的方式是不要连写。

例如下面两个语句,它们在最终测试结果上是等价的。第一个语句先测试可写性,再测试可读性,只有两者均为真时if条件才为真。
print "writable and readable\n" if -r -w "/tmp/fa.log";
print "writable and readable\n" if -w -r "/tmp/fa.log";

但返回非真/假值的测试符,需要非常小心。例如-s返回的是文件字节数:
@arr=`ls`;
foreach (@arr){
    chomp;
    if (-s -f $_ < 512){    # 这里的结果会出乎意料
        print "${_}'s size < 512 bytes\n";
    }
}

上面的if条件子句等价于(-f $_ and -s _) < 512,它会输出小于512字节的普通文件,以及所有非普通文件。因为and是短路的,如果测试的目标$_不是一个普通文件,而是一个目录,-f $_就会返回假,并结束测试,然后这部分表达式和512做数值比较,假对应的数值是0,它永远会返回真。

所以对于返回非真/假值的测试符,应该避免测试符连写:
@arr=`ls`;
foreach (@arr){
    chomp;
    if (-f $_ and -s _ < 512){
        print "${_}'s size < 512 bytes\n";
    }
}

stat函数和lstat函数

虽然文件测试符有很多,且测试的属性来源都是stat函数,但stat函数返回的信息(共13项属性)比支持的文件测试符还要多。

注:Unix操作系统里有一个stat命令,它也是返回文件的属性信息,和perl的内置stat函数基本类似。

它返回13项属性先后顺序分别是:
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

属性:意义
-------------------------------
dev:文件所属文件系统的设备ID
inode:文件inode号码
mode:文件类型和文件权限(两者都是数值表示)
nlink:文件硬链接数
uid:文件所有者的uid
gid:文件所属组的gid
rdev:文件的设备ID(只对特殊文件有效,即设备文件)
size:文件大小,单位字节
atime:文件atime的时间戳(从1970-01-01开始计算的秒数)
mtime:文件mtime的时间戳(从1970-01-01开始计算的秒数)
ctime:文件ctime的时间戳(从1970-01-01开始计算的秒数)
blksize:文件所属文件系统的block大小
blocks:文件占用block数量(一般是512字节的块大小,可通过unix的stat -c "%B"获取块的字节)

需要注意的是,$mode返回的是文件类型和文件权限的结合体,且文件权限并非直接的8进制权限值,要计算出Unix系统中直观的权限数值(如0755、0644),需要和0777做位运算。

use 5.010;
my $filename=$ARGV[0];
my @arr = my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

常用的位移是2、7、8、9,分别用来获取权限、大小、atime和mtime属性。这几项可以记忆下来或perldoc -f stat查看。

如果要计算文件权限,则:
printf "perm:%04o\n",$mode & 0777;    # 将返回0644、0755类型的权限值

也可以直接一点取出某项属性的值:my $mode = (stat($filename))[2];

以下是stat函数的其它一些注意事项:
1.stat函数返回布尔值表示是否成功stat:
    1.1.如果stat成功,则立即设置这些属性变量,并缓存到特殊文件句柄_
    1.2.如果stat失败,则返回空列表
2.如果stat函数测试的是特殊文件句柄_,它将不会重新测试缓存文件,而是直接返回缓存的属性信息
3.如果省略stat函数的参数,则默认测试$_

对于软链接文件,stat会追踪到链接的目标。如果不想追踪,则使用lstat函数替代stat。lstat如果测试的目标不是软链接,则返回空列表。


理解 Effective UID (EUID) 和 Real UID (RUID)

每个文件都有其所有者和所属组以及权限位属性:
$ ls -l /etc/hosts
-rw-r--r-- 1 root root 619 Oct  1 08:51 /etc/hosts

文件上的用户信息和权限信息是文件属性,是静态的,用来限制谁能对该文件执行什么操作。而对文件执行操作最终是由进程完成的,所以进程执行操作时拥有的权限才是决定能否对文件执行操作的标准。

Effective UID (EUID) 称为有效用户 ID,Real UID 称为实际用户 ID。还有对应的 EGID 和 RGID。它们都是针对进程而非针对文件的,也就是说它们是进程的属性,而非文件的属性。

进程在执行某些涉及权限的操作时,内核将使用进程的【有效用户/组 ID】作为凭证来判断是否有权限执行对应操作。例如,rm 命令删除文件 a.txt 时,内核将使用 rm 进程的有效用户 ID 和有效组 ID 和 a.txt 文件的用户 ID 和组 ID 做比较,进而再比较对应的权限位,从而判断 rm 进程是否有权限删除 a.txt。

既然涉及到权限的操作都以有效 ID 作为依据,那实际 ID 有什么作用?RUID 和 RGID 用于确定进程所属的用户和组,主要用于判断是否有权限向进程发送信号:对于非特权用户,如果发送信号的进程 A 的 RUID 和目标进程 B 的 RUID,则进程 A 可以发送信号。由于子进程会继承父进程的实际 ID,所以父子进程的 RUID 相等,父子进程可互相发信号。

用户执行程序时,默认将当前用户或指定的用户 (如 sudo 方式) 设置为该进程的有效用户 ID 和实际用户 ID。此时它们是相等的。

进程内部可通过代码修改进程的有效用户 ID。例如以 root 身份运行的程序,初始时其有效用户 ID 为 0 (即 root),具有特权,之后在程序内部修改其有效用户 ID 为 nobody,修改后该进程将失去大量权限。也可以在程序文件上设置 suid,使得该程序在执行时,其有效用户 ID 被设置为程序文件的 UID。

# /bin/passwd 设置了SUID,执行passwd时,passwd进程的有效用户ID将设置为root
$ ls -l /bin/passwd  
-rwsr-xr-x 1 root root 68208 Apr 16 20:36 /bin/passwd

举个例子来分析。用户 user1 执行 /bin/app 删除 a.txt 文件时的身份设置过程:只考虑用户不考虑组。

1.获取 /bin/app 权限信息,判断 user1 是否有权限执行该程序,若有权,则 exec 加载成功。
2.如果 app 文件未设置 SUID,则设置进程的有效用户 ID 和实际用户 ID 都为 user1。
3.如果 app 文件设置了 SUID,则设置进程的有效用户 ID 为 app 文件的用户 ID。
4.app 程序内部可能也会通过系统调用修改有效用户 ID。
5.app 进程执行删除 a.txt 文件代码时,内核将比较此时 app 进程的有效 ID 和 a.txt 文件的用户 ID,从而判断权限位。

此外还有保存的 UID (Saved UID) 和保存的 GID (Saved GID),保存的 ID 是保存有效 ID 的。在程序启动的时候,会将保存的 ID 设置为有效 ID 相同的值,之后如果修改有效 ID,保存 ID 将不会改变。

通过 ps 命令可以查看进程的 euid、ruid 和 suid。
$ ps -o pid,euid,ruid,suid,command
  PID  EUID  RUID  SUID COMMAND
 2112  1000  1000  1000 -bash
 6006  1000  1000  1000 ps -o pid,euid,ruid,suid,command

服务类程序权限配置的惯用方式

对于服务类程序,经常允许用户去配置以哪个 User 和 Group (通常配置为非特权身份以求安全) 去运行服务。对于这类服务进程,惯用的手段是:
1.程序启动时,先执行一些需要特权才能执行的操作(如创建 /var/run/xx.pid)。
2.然后备份当前具有特权的 EUID。
3.修改有效 GID、实际 GID 和辅助组(除了有效 ID,辅助组也会影响内核检查权限)。
    3.1.之所以在修改 EUID 之前修改组,因为修改 EUID 之后就丢失了权限,无法再修改组。
4.修改 EUID 为非特权用户(比如 nobody,一般可在配置文件中指定)。
    4.1.修改 EUID 后,进程将丢失大量权限,可保证进程是安全的。
5.在进程退出时,恢复 EUID 为具有特权的用户身份。
    5.1.之所以要恢复特权 EUID,是可能需要做一些要求特权的清理善后操作,例如删除 /var/run/xx.pid。

例如下面是一段 perl 实现服务类程序的部分代码:
use constant PIDPATH => '/var/run'; # 只有root可操作

sub init_server{
  my ($user, $group);
  ($pidfile, $user, $group) = @_;
  $pidfile = shift || getpidfilename();
  my $fh = open_pid_file($pidfile);    # 只有root可创建、删除、打开/var/run下的pid文件
  become_daemon();
  print $fh $$;
  close $fh;
  init_log();
  # 在change_privileges之前先执行一些需要特权的操作,之后修改为非特权EUID
  change_privileges($user, $group) if defined $user && defined $group;
  return $pid = $$;
}

sub change_privileges{
  my ($user, $group) = @_;
  my $uid = getpwnam($user) or die "Can't get uid for $user\n";
  my $gid = getgrnam($group) or die "Can't get gid for $group\n";
  # 和UID/GID相关的几个perl变量
  #    $>: 有效UID
  #    $): 有效GID
  #    $<: 实际UID
  #    $(: 实际GID
  $) = "$gid $gid";    # 修改EGID,同时将辅助组也设置为EGID,表示辅助组设置为空
  $( = $gid;    # 修改RGID
  $> = $uid;    # 修改EUID,从此开始将丢失权限
}

END{
  $> = $<; # 恢复特权EUID,以便删除要求权限的pid文件
  unlink $pidfile if defined $pid and $$ == $pid
}