Perl文件测试运算符-if为符号链接返回true

时间:2012-04-25 08:50:46

标签: perl shell unix find

我曾经认为-f测试了一个文件,看它是不是常规文件,而不是其他任何文件。但Perl的行为似乎有所不同。我查了perldoc条目,然后说:

-f  File is a plain file.

假设我的目录中有一个名为file1的文件和5个符号链接1 2 3 4 5,每个文件都指向file1,如下所示:

-rw-r--r-- file1
lrwxrwxrwx 1 -> file1
lrwxrwxrwx 2 -> file1
lrwxrwxrwx 3 -> file1
lrwxrwxrwx 4 -> file1
lrwxrwxrwx 5 -> file1
drwxr-xr-x ../
drwxr-xr-x ./

如果我使用-type f过滤器在此目录上运行find,它会按预期提供输出:

%  find . -type f
./file1

但是当我使用-f运算符运行perl脚本时,它会提供以下输出:

%  ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ }'
1
2
3
4
5
file1

当我添加-l的测试时,它按预期工作:

%  ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ and not -l $_}'
file1

符号链接是否也被视为普通文件?如果是这样,为什么?我对文件测试的使用是否不正确?

3 个答案:

答案 0 :(得分:17)

当您测试符号链接时,除非您使用-l符号链接测试,否则将对符号链接指向的内容执行测试。

这与行为相似的statlstat Linux系统调用相似。也就是说,如果您stat符号链接,您将获得符号链接目标的结果,而如果lstat符号链接,您将获得符号链接本身的结果。这种行为是故意的,因此天真的程序不必关心符号链接,符号链接将按预期工作。

您应该会发现,如果您的符号链接引用目录,则-f测试为false且-d测试为真。

答案 1 :(得分:4)

快速解决方案

$ ls | perl -lne 'print if stat && -f _'
1
2
3
4
5
file1

$ ls | perl -lne 'print if lstat && -f _'
file1

符号链接并找到

默认情况下,GNU find从不取消引用或遵循符号链接,但find documentation描述了更改此政策的开关。

  

控制关于链接的find行为的选项如下: -

     

-P
  find根本不会取消引用符号链接。这是默认行为。必须在命令行上的任何文件名之前指定此选项。

     

-H
  find不会取消引用符号链接(除了命令行上的文件名,它们被解除引用)。如果无法解除引用符号链接,则使用符号链接本身的信息。必须在命令行上的任何文件名之前指定此选项。

     

-L
  find在可能的情况下解除引用符号链接,并且在不可能的情况下,它使用符号链接本身的属性。必须在命令行上的任何文件名之前指定此选项。使用此选项还意味着与-noleaf选项具有相同的行为。如果您以后使用-H-P选项,则不会关闭-noleaf

     

-follow
  此选项构成“表达式”的一部分,必须在文件名后指定,但它等同于-L-follow选项仅影响在命令行上显示的那些测试。不推荐使用此选项。如果可能,您应该使用-L代替。

将查找命令转换为Perl

标准发行版附带一个find2perl实用程序,该实用程序与旧版Unix系统中的find兼容。

$ find2perl . -type f | perl
./file1

我们可以要求提供纯文件本身或普通文件链接的文件。

$ find2perl . -follow -type f | perl
./1
./2
./3
./4
./5
./file1

在生成的代码find2perl中,传递给find from the File::Find module的默认wanted子信息是

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _
    && print("$name\n");
}

但是-follow,我们得到了

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = stat($_)) &&
    -f _
    && print("$name\n");
}

请注意,唯一的区别是wantedstat还是lstat,后者记录为

  

<强> lstat EXPR
  的 lstat

     

stat函数相同(包括设置特殊_文件句柄)但stats是符号链接而不是符号链接指向的文件。如果您的系统未实现符号链接,则会执行正常的stat。有关更详细的信息,请参阅stat的文档。

     

如果省略 EXPR ,则统计信息$_

find2perl显示的示例输出显示,您可以使用filetest运算符表达您的意图,但要精确了解符号链接的语义,并选择statlstat

那个有趣的_令牌

上面快速解决方案末尾的_lstat documentation提到的特殊文件句柄。它包含statlstat的最新结果的副本,以避免重复进行那些昂贵的系统调用。 -f-r-e-lFiletest operators也会填充此缓冲区:

  

如果任何文件测试(或statlstat运算符)被赋予由单独下划线组成的特殊文件句柄,那么前一个文件测试的统计结构(或{{使用1}}运算符),保存系统调用。 (这不适用于stat,您需要记住-tlstat在符号链接的统计结构中保留值,而不是真实文件。(另外,如果统计缓冲区被-l调用填充,lstat-T将使用-B的结果重置它。例如:

stat _

答案 2 :(得分:3)

默认情况下,所有文件测试运算符(-l除外)都使用stat()进行测试,这意味着它们对符号链接是透明的。 -f在常规文件上返回true,或在常规文件中返回符号链接。

为了使用lstat(),您应首先lstat然后使用特殊_文件句柄上的文件测试,该文件句柄存储来自最新stat的结果或lstat操作。

perl -e 'while(<>) { chomp; print "$_\n" if lstat $_ && -f _ }'