Perl 的文件测试运算符 -f 对符号链接返回 true

Posted

技术标签:

【中文标题】Perl 的文件测试运算符 -f 对符号链接返回 true【英文标题】:Perl's file test operator -f returns true for symbolic links 【发布时间】:2012-05-05 22:33:04 【问题描述】:

我曾经认为-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 . -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

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

【问题讨论】:

相关:-xstat 系统调用的快捷方式。如果想自己区分符号链接,请使用lstat 【参考方案1】:

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

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

您应该发现,如果您的符号链接指向一个目录,-f 测试为假,-d 测试为真。

【讨论】:

+1。 perldoc stat, perdoc lstat【参考方案2】:

快速解决方案

$ ls | perl -lne '打印如果 stat && -f _'
1
2
3
4
5
文件 1

$ ls | perl -lne '打印如果 lstat && -f _'
文件1

符号链接和查找

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

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

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

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

-Lfind 在可能的情况下取消引用符号链接,在不可能的情况下,它使用符号链接本身的属性。此选项必须在命令行上的任何文件名之前指定。使用此选项也意味着与-noleaf 选项相同的行为。如果您稍后使用-H-P 选项,这不会关闭-noleaf

-follow 此选项构成“表达式”的一部分,必须在文件名之后指定,但在其他方面等同于-L-follow 选项仅影响出现在命令行上的那些测试。此选项已弃用。如果可能,您应该改用-L

将查找命令转换为 Perl

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

$ find2perl 。 -类型 f | perl
./file1

我们可以改为要求文件本身是纯文件或指向纯文件的链接。

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

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

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");

请注意,唯一的区别是wanted 调用stat 还是lstat,后者记录为

lstat <i>EXPR</i>lstat

stat 函数做同样的事情(包括设置特殊的_ 文件句柄)但统计符号链接而不是符号链接指向的文件。如果您的系统上未实现符号链接,则正常的stat 已完成。有关更多详细信息,请参阅stat 的文档。

如果省略EXPR,则统计$_

正如 find2perl 的示例输出所示,您可以使用 filetest 运算符表达您的意图,但可以通过选择 statlstat 来准确了解符号链接的语义。

那个有趣的_令牌

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

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

print "Can do.\n" if -r $a || -w _ || -x _;

stat($filename);
print "Readable\n" if -r _;
print "Writable\n" if -w _;
print "Executable\n" if -x _;

【讨论】:

我认为这里的简短版本是,如果您按名称进行文件测试,则查找将始终遵循符号链接,但如果您使用 statlstat 加上 _ 句柄上的文件测试,你可以控制是否关注。 @hobbs 感谢您的微妙建议!已更新。 从 Adrian 的回答中,我了解到所有的魔法/逻辑都在于 statlstat 函数。但后来我无法完全关联find 的结果,而man find 有点难以理解。非常感谢文档链接和解释。【参考方案3】:

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

要改用lstat(),您应该首先使用lstat,然后在特殊的_ 文件句柄上使用文件测试,该文件句柄存储最近的statlstat 操作的结果。

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

【讨论】:

以上是关于Perl 的文件测试运算符 -f 对符号链接返回 true的主要内容,如果未能解决你的问题,请参考以下文章

Perl 变量:数组变量

使用Perl中的Find查找符号链接

使用Unix readlink命令的Perl递归循环符号链接最终目的地

为啥 '<' 和 'lt' 运算符在 Perl 中返回不同的结果?

如何真正看到链接到一个文件的不同 perl 脚本的内容?

将符号链接及其目标与 bash test -ef 进行比较