目錄

名稱

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 變更

2.0 分支中的 API 已更改。 有一段時間,mkpathrmtree 嘗試,但未成功地處理兩種不同的調用機制。 這種方法被認為是失敗的。

新的語義現在僅通過 make_pathremove_tree 可用。 老的語義僅通過 mkpathrmtree 可用。 強烈建議用戶升級至至少 2.08 以避免意外情況。

安全考量

在 1.x 版本的 File::Path 的 rmtree 函數中存在競態條件(儘管有時根據 OS 分發或平台進行了修補)。 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 被中斷,最初處於只讀模式的文件現在可能將其權限設置為讀寫模式(或“允許刪除”模式)。

以下 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] (SEVERE)

make_path 無法創建路徑。可能是在起始點處的某種權限錯誤或在 Unix 上的空閒 inode 不足等資源問題。

未指定根路徑

make_path 沒有給定任何要創建的路徑。只有在使用傳統接口調用該例程時,才會發出此消息。如果給予現代接口要做的事情,則該接口將保持沉默。

找不到文件或目錄

在 Windows 上,如果 make_path 給出此警告,可能意味著您已超出文件系統的最大路徑長度。

無法獲取初始工作目錄:[errmsg]

remove_tree 嘗試通過調用 Cwd::getcwd 來確定初始目錄,但調用失敗。將不會嘗試刪除任何東西。

無法 stat 初始工作目錄:[errmsg]

remove_tree 嘗試對初始目錄進行 stat(在成功獲取其名稱後通過 getcwd),但調用失敗。將不會嘗試刪除任何東西。

無法 chdir 到 [dir]:[errmsg]

remove_tree 嘗試設置工作目錄以開始刪除其中的對象,但失敗了。這通常是權限問題。該例程將繼續刪除其他東西,但此目錄將保持完整。

目錄 [dir] 在 chdir 前已更改,預期 dev=[n] ino=[n],實際 dev=[n] ino=[n],中止。 (致命)

remove_tree 記錄了目錄的裝置和 i 節點,然後移動到該目錄中。然後對當前目錄執行了 stat,檢測到裝置和 i 節點不再相同。由於這是競態條件問題的核心,程序將在此時終止。

無法將目錄 [dir] 設為可讀寫:[errmsg]

remove_tree 嘗試更改當前目錄的權限,以確保後續的取消連結不會遇到問題,但未能成功。權限保持不變,程序將繼續運行,盡其所能。

無法讀取 [dir]:[errmsg]

remove_tree 試圖讀取目錄的內容,以獲取要取消連結的目錄項的名稱,但未成功。這通常是權限問題。程序將繼續運行,但該目錄中的文件將保留在調用後。

無法重置 chmod [dir]:[errmsg]

remove_tree 在刪除目錄中的所有內容後,試圖恢復其權限為原始狀態,但失敗了。可能會遺留該目錄。

當 cwd 為 [dir] 時無法刪除 [dir]

程序的當前工作目錄是 /some/path/to/here,您正在嘗試刪除一個祖先目錄,如 /some/path。目錄樹保持不變。

解決方法是將目錄切換出子目錄,到一個目錄樹之外的地方。

無法從 [child-dir] 切換到 [parent-dir]:[errmsg],正在中止。 (致命錯誤)

remove_tree 在刪除所有內容並恢復目錄權限後,無法切換回父目錄。程序停止以避免發生競態條件。

無法 stat 先前的工作目錄 [dir]:[errmsg],正在中止。 (致命錯誤)

remove_tree 從子目錄返回後,無法對父目錄進行狀態統計。由於無法確定是否返回到預期的位置(通過比較裝置和inode),唯一的出路是 croak

進入子目錄 [child-dir] 前,前一目錄 [parent-dir] 的變更,預期裝置=[n] inode=[n],實際裝置=[n] inode=[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::Pathrmtreeremove_tree 將不會與多緒應用程式一起工作,因為它使用了 chdir。目前,此情況不會產生警告或錯誤。您將確實遇到意外的結果。

顯示此限制的實現將不會更改。請參閱 File::Path::Tiny 模組,以獲得與 File::Path 類似但不使用 chdir 的功能。

NFS 掛載點

File::Path 不負責觸發自動掛載、鏡像掛載和網絡掛載文件系統的內容。如果您的 NFS 實現需要在文件系統上執行操作,以便 File::Path 執行操作,強烈建議您通過讀取已掛載文件系統的根目錄來確保文件系統的可用性。

報告錯誤

請透過 RT 隊列報告所有的錯誤,可以透過網頁界面

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

或者透過電子郵件

bug-File-Path@rt.cpan.org

無論哪種情況,請務必將修補程式附加到錯誤報告中,而不是將其內嵌在網路文章或電子郵件正文中。

您也可以將拉取請求發送到 Github 存儲庫

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

鳴謝

Paul Szabo 最初識別了競爭條件,Brendan O'Dea 為 Debian 編寫了解決問題的實現。該代碼被用作當前代碼的基礎。感謝他們的努力。

Gisle Aas 在 2.07 版本的文檔中進行了許多改進,對他的建議和幫助也表示衷心的感謝。

作者

之前的作者和維護者:Tim Bunce、Charles Bailey 和 David Landgren <david@landgren.net>。

當前的維護者是 Richard Elberger <riche@cpan.org> 和 James (Jim) Keenan <jkeenan@cpan.org>。

貢獻者

按照名字的字母順序列出的 File::Path 的貢獻者。

<bulkdd@cpan.org>
Charlie Gonzalez <itcharlie@cpan.org>
Craig A. Berry <craigberry@mac.com>
James E Keenan <jkeenan@cpan.org>
John Lightsey <john@perlsec.org>
Nigel Horne <njh@bandsman.co.uk>
Richard Elberger <riche@cpan.org>
Ryan Yee <ryee@cpan.org>
Skye Shaw <shaw@cpan.org>
Tom Lutz <tommylutz@gmail.com>
Will Sheppard <willsheppard@github>

版權

此模組版權所有 (C) Charles Bailey、Tim Bunce、David Landgren、James Keenan 和 Richard Elberger 1995-2020。保留所有權利。

許可協議

本程式庫是自由軟體;您可以按照 Perl 本身的相同條款重新分發或修改它。