内容

名称

File::Path - 创建或删除目录树

版本

2.18 - 发布日期 2020 年 11 月 4 日。

概要

use File::Path qw(make_path remove_tree);

@created = make_path('foo/bar/baz', '/zug/zwang');
@created = make_path('foo/bar/baz', '/zug/zwang', {
    verbose => 1,
    mode => 0711,
});
make_path('foo/bar/baz', '/zug/zwang', {
    chmod => 0777,
});

$removed_count = remove_tree('foo/bar/baz', '/zug/zwang', {
    verbose => 1,
    error  => \my $err_list,
    safe => 1,
});

# legacy (interface promoted before v2.00)
@created = mkpath('/foo/bar/baz');
@created = mkpath('/foo/bar/baz', 1, 0711);
@created = mkpath(['/foo/bar/baz', 'blurfl/quux'], 1, 0711);
$removed_count = rmtree('foo/bar/baz', 1, 1);
$removed_count = rmtree(['foo/bar/baz', 'blurfl/quux'], 1, 1);

# legacy (interface promoted before v2.06)
@created = mkpath('foo/bar/baz', '/zug/zwang', { verbose => 1, mode => 0711 });
$removed_count = rmtree('foo/bar/baz', '/zug/zwang', { verbose => 1, mode => 0711 });

描述

此模块提供了一种方便的方法来创建任意深度的目录,并从文件系统中删除整个目录子树。

提供以下函数

make_path( $dir1, $dir2, .... )
make_path( $dir1, $dir2, ...., \%opts )

make_path 函数在这些目录不存在的情况下创建它们,类似于 Unix 命令 mkdir -p

该函数接受要创建的目录列表。其行为可以通过作为调用中最后一个参数出现的可选哈希引用来调整。

该函数返回在调用期间实际创建的目录列表;在标量上下文中,返回创建的目录数量。

选项哈希中识别以下键

mode => $num

要应用于每个创建目录的数字权限模式(默认为 0777),由当前 umask 修改。如果目录已存在(因此不需要创建),则不会修改权限。

mask 被识别为该参数的别名。

chmod => $num

接受一个数字模式,应用于每个创建的目录(不受当前 umask 的影响)。如果目录已经存在(因此不需要创建),则权限不会被修改。

verbose => $bool

如果存在,将导致 make_path 在创建每个目录时打印其名称。默认情况下不会打印任何内容。

error => \$err

如果存在,它应该是一个标量引用。这个标量将被设置为引用一个数组,该数组将用于存储遇到的任何错误。有关更多信息,请参见 "错误处理" 部分。

如果未使用此参数,某些错误条件可能会引发致命错误,导致程序停止,除非在 eval 块中捕获。

owner => $owner
user => $owner
uid => $owner

如果存在,将导致任何创建的目录由 $owner 拥有。如果该值是数字,它将被解释为 uid;否则将假定为用户名。如果用户名无法映射到 uid,uid 不存在或进程缺乏更改所有权的权限,将发出错误。

已经存在的目录的所有权不会改变。

useruidowner 的别名。

group => $group

如果存在,将导致任何创建的目录由组 $group 拥有。如果该值是数字,它将被解释为 gid;否则将假定为组名。如果组名无法映射到 gid,gid 不存在或进程缺乏更改组所有权的权限,将发出错误。

已存在目录的组所有权不会更改。

make_path '/var/tmp/webcache', {owner=>'nobody', group=>'nogroup'};
mkpath( $dir )
mkpath( $dir, $verbose, $mode )
mkpath( [$dir1, $dir2,...], $verbose, $mode )
mkpath( $dir1, $dir2,..., \%opt )

mkpath() 函数提供 make_path() 的传统接口,但对传递的参数有不同的解释。除了参数解释之外,该函数的行为和返回值与 make_path() 相同。

remove_tree( $dir1, $dir2, .... )
remove_tree( $dir1, $dir2, ...., \%opts )

remove_tree 函数会删除给定的目录以及它们可能包含的任何文件和子目录,类似于 Unix 命令 rm -rf 或 Windows 命令 rmdir /srd /s

该函数接受要删除的目录列表。(实际上,它也会接受不是目录的文件系统条目,例如普通文件和符号链接。但正如其名称所暗示的那样,它的目的是删除树而不是单个文件。)

remove_tree() 的行为可以通过一个可选的哈希引用来调整,该哈希引用作为调用中的最后一个参数出现。如果向 remove_tree 传递空字符串,则会发生错误。

注意:出于安全原因,我们强烈建议使用哈希引用作为最终参数的语法 - 特别是将 safe 元素设置为真值。

remove_tree( $dir1, $dir2, ....,
    {
        safe => 1,
        ...         # other key-value pairs
    },
);

该函数返回成功删除的文件数量。

选项哈希中识别以下键

verbose => $bool

如果存在,将导致 remove_tree 在取消链接每个文件时打印其名称。默认情况下不会打印任何内容。

safe => $bool

当设置为真值时,将导致 remove_tree 跳过进程缺乏删除文件所需权限的文件,例如 VMS 上的删除权限。换句话说,代码不会尝试更改文件权限。因此,如果进程被中断,则不会将任何文件系统对象保留在更宽松的模式下。

keep_root => $bool

如果设置为真值,将删除所有文件和子目录,除了最初指定的目录。这在清理应用程序的临时目录时非常有用。

remove_tree( '/tmp', {keep_root => 1} );
result => \$res

如果存在,它应该是一个标量引用。这个标量将被设置为引用一个数组,该数组将用于存储在调用期间未链接的所有文件和目录。如果没有任何内容被链接,则数组将为空。

remove_tree( '/tmp', {result => \my $list} );
print "unlinked $_\n" for @$list;

这是 verbose 键的有用替代方案。

error => \$err

如果存在,它应该是一个标量引用。这个标量将被设置为引用一个数组,该数组将用于存储遇到的任何错误。有关更多信息,请参见 "错误处理" 部分。

删除东西比创建东西危险得多。因此,remove_tree 可能遇到某些条件,这些条件非常危险,以至于唯一合理的行动就是终止程序。

使用 error 来捕获所有合理的情况(权限问题等),如果事情失控,就让它死掉。这是最安全的做法。

rmtree( $dir )
rmtree( $dir, $verbose, $safe )
rmtree( [$dir1, $dir2,...], $verbose, $safe )
rmtree( $dir1, $dir2,..., \%opt )

rmtree() 函数提供了 remove_tree() 的遗留接口,对传递的参数有不同的解释。该函数的行为和返回值与 remove_tree() 相同。

注意:出于安全原因,我们强烈建议使用哈希引用作为最终参数的语法,特别是将 safe 元素设置为真值。

rmtree( $dir1, $dir2, ....,
    {
        safe => 1,
        ...         # other key-value pairs
    },
);

错误处理

注意:

以下错误处理机制在所有代码路径中都是一致的,除了根节点不存在的情况。在 2.11 版本中,维护者试图纠正这种不一致,但太多下游模块遇到了问题。在这种情况下,如果您需要在调用 make_pathremove_tree 之前评估根节点或进行错误检查,您应该采取额外的预防措施。

如果 make_pathremove_tree 遇到错误,将通过 carp(对于非致命错误)或 croak(对于致命错误)向 STDERR 打印诊断消息。

如果这种行为不可取,可以使用 error 属性来保存对变量的引用,该变量将用于存储诊断信息。该变量被设置为对一个哈希引用数组的引用。每个哈希包含一个键值对,其中键是文件名称,值是错误消息(包括 $! 的内容,如果适用)。如果遇到一般错误,诊断键将为空。

一个示例用法如下

remove_tree( 'foo/bar', 'bar/rat', {error => \my $err} );
if ($err && @$err) {
    for my $diag (@$err) {
        my ($file, $message) = %$diag;
        if ($file eq '') {
            print "general error: $message\n";
        }
        else {
            print "problem unlinking $file: $message\n";
        }
    }
}
else {
    print "No error encountered\n";
}

请注意,如果未遇到任何错误,$err 将引用一个空数组。这意味着 $err 将始终为 TRUE;因此,您需要测试 @$err 以确定是否发生了错误。

注释

File::Path 盲目地将 mkpathrmtree 导出到当前命名空间。如今,这被认为是不好的风格,但现在更改它会导致太多代码中断。尽管如此,您仍然可以指定您期望使用什么。

use File::Path 'rmtree';

例程 make_pathremove_tree 不会 默认导出。您必须指定要使用哪些。

use File::Path 'remove_tree';

请注意,上述的一个副作用是 mkpathrmtree 现在不再导出。这是由于 Exporter 模块的工作方式。如果您要将代码库迁移到使用新接口,则必须显式列出所有内容。但这始终是好的做法。

use File::Path qw(remove_tree rmtree);

API 更改

API 在 2.0 分支中进行了更改。有一段时间,mkpathrmtree 尝试(不成功)处理两种不同的调用机制。这种方法被认为是失败的。

新的语义现在仅适用于 make_pathremove_tree。旧的语义仅通过 mkpathrmtree 可用。强烈建议用户升级到至少 2.08 版本以避免意外情况。

安全注意事项

File::Path 的 rmtree 函数的 1.x 实现中存在竞争条件(尽管有时根据操作系统发行版或平台进行修补)。2.0 版本包含代码以避免 CVE-2002-0435 中提到的问题。

有关更多信息,请参阅以下页面

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=286905
http://www.nntp.perl.org/group/perl.perl5.porters/2005/01/msg97623.html
http://www.debian.org/security/2005/dsa-696

此外,除非设置了 safe 参数(或传统接口中的第三个参数为 TRUE),否则如果 remove_tree 被中断,最初处于只读模式的文件现在可能将其权限设置为读写(或“删除 OK”)模式。

以下 CVE 报告以前针对 File-Path 提交,据信已得到解决

2017 年 2 月,cPanel 安全团队报告了 File-Path 中的另一个漏洞。用于使目录可遍历的 chmod() 逻辑可被滥用,以将攻击者选择的攻击者选择的模式设置为攻击者选择的模式。这是由于 stat()(决定 inode 是目录)和 chmod()(尝试使其成为用户-rwx)之间的检查时间到使用时间 (TOCTTOU) 竞争条件 (https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use)。CPAN 版本 2.13 及更高版本包含 John Lightsey 提供的补丁以解决此问题。此漏洞已报告为 CVE-2017-6512。

诊断

致命错误会导致程序停止(croak),因为问题非常严重,以至于继续运行会很危险。(这始终可以通过 eval 捕获,但这不是一个好主意。在这种情况下,死亡是最好的选择)。

可以使用现代接口捕获严重错误。如果未捕获这些错误,或使用旧接口,则此类错误会导致程序停止运行。

所有其他错误可以使用现代接口捕获,否则会发出carp警告。程序执行不会停止。

mkdir [path]: [errmsg] (严重)

make_path无法创建路径。可能是出发点存在某种权限错误,或者资源不足(例如 Unix 上的空闲 inode)。

未指定根路径。

make_path未获得任何要创建的路径。仅当使用传统接口调用该例程时才会发出此消息。如果现代接口没有要执行的操作,则会保持静默。

没有这样的文件或目录

在 Windows 上,如果make_path向您发出此警告,则可能意味着您已超过文件系统的最大路径长度。

无法获取初始工作目录: [errmsg]

remove_tree尝试通过调用Cwd::getcwd来确定初始目录,但调用由于某种原因失败。不会尝试删除任何内容。

无法统计初始工作目录: [errmsg]

remove_tree尝试统计初始目录(在成功通过getcwd获取其名称后),但是调用由于某种原因失败。不会尝试删除任何内容。

无法更改到 [dir]: [errmsg]

remove_tree尝试设置工作目录以开始删除其中的对象,但未成功。这通常是权限问题。该例程将继续删除其他内容,但此目录将保持不变。

目录 [dir] 在更改目录之前已更改,预期 dev=[n] ino=[n],实际 dev=[n] ino=[n],中止。 (严重)

remove_tree 记录了目录的设备和 inode,然后移入其中。然后,它对当前目录执行了 stat 操作,并检测到设备和 inode 不再相同。由于这是竞争条件问题的核心,程序将在此时终止。

无法将目录 [dir] 设置为可读写: [errmsg]

remove_tree 尝试更改当前目录的权限,以确保后续的删除操作不会遇到问题,但未能成功。权限保持原样,程序将继续执行,尽其所能。

无法读取 [dir]: [errmsg]

remove_tree 尝试读取目录的内容以获取要删除的目录项的名称,但未成功。这通常是权限问题。程序将继续执行,但此目录中的文件将在调用后保留。

无法重置 [dir] 的 chmod: [errmsg]

remove_tree 在删除目录中的所有内容后,尝试将其权限恢复到原始状态,但失败了。该目录可能会被遗留下来。

无法在 cwd 为 [dir] 时删除 [dir]

程序的当前工作目录为 /some/path/to/here,您正在尝试删除其祖先目录,例如 /some/path。目录树保持不变。

解决方案是使用 chdir 从子目录切换到要删除的目录树之外的位置。

无法从 [child-dir] 切换到 [parent-dir]: [errmsg],中止。 (致命错误)

remove_tree 在删除所有内容并恢复目录的权限后,无法切换回父目录。程序停止执行,以避免发生竞争条件。

无法统计先前工作目录 [dir]: [errmsg],中止。 (致命错误)

remove_tree 在从子目录返回后无法获取父目录的状态。由于无法确定我们是否回到了我们认为应该在的位置(通过比较设备和 inode),唯一的解决办法是 croak

进入 [child-dir] 之前,父目录 [parent-dir] 已发生更改,预期 dev=[n] ino=[n],实际 dev=[n] ino=[n],中止。 (致命)

remove_tree 从删除子目录中的文件返回时,检查发现它返回到的父目录不是它开始时的目录。这被认为是恶意活动的迹象。

无法将目录 [dir] 设置为可写: [errmsg]

在删除目录之前(成功删除了目录中的所有内容后),remove_tree 尝试设置目录的权限以确保它可以被删除,但失败了。程序执行继续,但目录可能无法被删除。

无法删除目录 [dir]: [errmsg]

remove_tree 尝试删除目录,但失败了。这可能是因为目录中仍然存在无法删除的对象,或者可能是权限问题。该目录将被保留。

无法将 [dir] 的权限恢复为 [0nnn]: [errmsg]

在无法删除目录后,remove_tree 无法将目录的权限从允许状态恢复到可能更严格的设置。(权限以八进制表示)。

无法将文件 [file] 设置为可写: [errmsg]

remove_tree 尝试强制文件权限以确保它可以被删除,但失败了。但是,它仍然会尝试解除文件链接。

remove_tree 无法删除文件。可能是权限问题。

无法将 [file] 的权限恢复为 [0nnn]: [errmsg]

在无法删除文件后,remove_tree 也无法将文件的权限恢复到可能更严格的设置。(权限以八进制表示)。

无法将 [owner] 映射到 uid,所有权未更改");

make_path 被指示将创建的目录的所有权赋予符号名称 [owner],但 getpwnam 未返回相应的数字 uid。目录将被创建,但所有权不会更改。

无法将 [group] 映射到 gid,组所有权未更改

make_path 被指示将创建的目录的组所有权赋予符号名称 [group],但 getgrnam 未返回相应的数字 gid。目录将被创建,但组所有权不会更改。

另请参阅

错误和限制

以下描述了 File::Path 的限制以及如何报告错误。

多线程应用程序

File::Path rmtreeremove_tree 由于使用 chdir,因此无法与多线程应用程序一起使用。目前,在这种情况下不会生成任何警告或错误。您肯定会遇到意外结果。

导致此限制的实现不会更改。请参阅 File::Path::Tiny 模块,以获取类似于 File::Path 的功能,但它不使用 chdir

NFS 挂载点

File::Path 不负责触发自动挂载、镜像挂载和网络挂载文件系统的內容。如果您的 NFS 实现需要对文件系统执行操作才能使 File::Path 执行操作,强烈建议您通过读取挂载文件系统的根目录来确保文件系统的可用性。

报告错误

请在 RT 队列上报告所有错误,可以通过 Web 界面

http://rt.cpan.org/NoAuth/Bugs.html?Dist=File-Path

或通过电子邮件

[email protected]

在任何情况下,请将补丁附加到错误报告,而不是将它们包含在 Web 帖子或电子邮件正文中。

您也可以向 Github 存储库发送拉取请求

https://github.com/rpcme/File-Path

致谢

Paul Szabo 最初发现了竞争条件,Brendan O'Dea 为 Debian 编写了一个解决该问题的实现。该代码被用作当前代码的基础。他们的努力非常感谢。

Gisle Aas 对 2.07 的文档进行了许多改进,他的建议和帮助也深表感谢。

作者

之前的作者和维护者:Tim Bunce、Charles Bailey 和 David Landgren <[email protected]>。

当前维护者是 Richard Elberger <[email protected]> 和 James (Jim) Keenan <[email protected]>。

贡献者

File::Path 的贡献者,按姓氏首字母顺序排列。

<[email protected]>
Charlie Gonzalez <[email protected]>
Craig A. Berry <[email protected]>
James E Keenan <[email protected]>
John Lightsey <[email protected]>
Nigel Horne <[email protected]>
Richard Elberger <[email protected]>
Ryan Yee <[email protected]>
Skye Shaw <[email protected]>
Tom Lutz <[email protected]>
Will Sheppard <willsheppard@github>

版权

本模块版权所有 (C) Charles Bailey, Tim Bunce, David Landgren, James Keenan 和 Richard Elberger 1995-2020。保留所有权利。

许可证

本库是自由软件;您可以根据与 Perl 本身相同的条款重新发布和/或修改它。