內容

名稱

File::Find - 遍歷目錄樹。

語法

use File::Find;
find(\&wanted, @directories_to_search);
sub wanted { ... }

use File::Find;
finddepth(\&wanted, @directories_to_search);
sub wanted { ... }

use File::Find;
find({ wanted => \&process, follow => 1 }, '.');

說明

這些函式用於搜尋目錄樹,並對找到的每個檔案執行類似於 Unix find 指令的工作。File::Find 匯出兩個函式,findfinddepth。它們的工作方式類似,但有細微的差別。

find
find(\&wanted,  @directories);
find(\%options, @directories);

find() 對指定的 @directories 執行深度優先搜尋,順序與指定的順序相同。對於找到的每個檔案或目錄,它會呼叫 &wanted 子常式。(請參閱下方有關如何使用 &wanted 函式的詳細資訊)。此外,對於找到的每個目錄,它會 chdir() 到該目錄中,並繼續搜尋,對目錄中的每個檔案或子目錄呼叫 &wanted 函式。

finddepth
finddepth(\&wanted,  @directories);
finddepth(\%options, @directories);

finddepth() 的運作方式與 find() 相同,但它是在呼叫目錄的 &wanted 函數之後才呼叫目錄的內容。它執行後序遍歷,而非預序遍歷,從目錄樹的底部往上執行,而 find() 則從樹的頂部往下執行。

儘管 finddepth() 函數的名稱,find()finddepth() 都會對目錄階層執行深度優先搜尋。

%options

find() 的第一個參數是 &wanted 函數的程式碼參考,或描述要為每個檔案執行的作業的雜湊參考。程式碼參考在下列的 "The wanted function" 中說明。

以下是雜湊的可能金鑰

wanted

值應該是程式碼參考。此程式碼參考在下列的 "The wanted function" 中說明。&wanted 子常式是強制性的。

bydepth

僅在報告完所有項目後才報告目錄的名稱。進入點 finddepth() 是在 find() 的第一個參數中指定 { bydepth => 1 } 的捷徑。

preprocess

值應該是程式碼參考。此程式碼參考用於預處理目前的目錄。目前處理中的目錄名稱在 $File::Find::dir 中。您的預處理函數在 readdir() 之後,但在呼叫 wanted() 函數的迴圈之前呼叫。它會以字串清單(實際上是檔案/目錄名稱)呼叫,並預期傳回字串清單。此程式碼可用於按字母順序、數字順序對檔案/目錄名稱排序,或根據名稱篩選目錄項目。當 followfollow_fast 生效時,preprocess 是 no-op。

postprocess

值應該是程式碼參考。它在離開目前處理中的目錄之前呼叫。它在無效內容中呼叫,沒有參數。目前目錄的名稱在 $File::Find::dir 中。此掛鉤很適合用於摘要目錄,例如計算其磁碟使用量。當 followfollow_fast 生效時,postprocess 是 no-op。

follow

導致追蹤符號連結。由於具有符號連結(已追蹤)的目錄樹可能包含檔案超過一次,甚至可能具有迴圈,因此必須建立一個雜湊,其中每個檔案都有項目。對於大型目錄樹而言,這在空間和時間上都可能很昂貴。請參閱下列的 "follow_fast""follow_skip"。如果 followfollow_fast 生效

  • 保證在呼叫使用者的 wanted() 函數之前已經呼叫過 lstat。這能夠進行涉及 _ 的快速檔案檢查。請注意,如果未設定 followfollow_fast,則不再提供此保證。

  • 有一個變數 $File::Find::fullname,它保存著檔案的絕對路徑名稱,其中所有符號連結都已解析。如果連結是一個懸空符號連結,則 fullname 將設定為 undef

follow_fast

這類似於 follow,但它可能會多次報告某些檔案。不過,它確實會偵測循環。由於只有符號連結必須進行雜湊,因此在空間和時間上都便宜得多。如果處理一個檔案多次(由使用者的 wanted() 函數)比僅花費時間更糟,則應使用選項 follow

follow_skip

follow_skip==1(這是預設值)會導致所有既不是目錄也不是符號連結的檔案在即將第二次處理時被忽略。如果一個目錄或一個符號連結即將第二次處理,File::Find 會中止。

follow_skip==0 會導致 File::Find 在任何檔案即將第二次處理時中止。

follow_skip==2 會導致 File::Find 忽略任何重複的檔案和目錄,但否則會正常進行。

指定如何處理目標不存在的符號連結。如果為 true 並且是程式碼參考,則會以符號連結名稱和它所在的目錄作為引數呼叫。否則,如果為 true 且警告開啟,則會發出表單 "symbolic_link_name is a dangling symbolic link\n" 的警告。如果為 false,則會靜默忽略懸空符號連結。

no_chdir

在遞迴時不會對每個目錄執行 chdir()。當然,wanted() 函數需要知道這一點。在這種情況下,$_ 將與 $File::Find::name 相同。

untaint

如果在 Taint 模式(-T 命令列開關或 EUID != UID 或 EGID != GID)下使用 find,則在執行 chdir 之前,目錄名稱在內部必須先解除污染。因此,它們會根據正規表示式 untaint_pattern 進行檢查。請注意,傳遞給使用者的 wanted() 函數的所有名稱仍然受到污染。如果在非 Taint 模式下使用此選項,則 untaint 是空運算。

untaint_pattern

請參閱上方。這應使用 qr 引用運算子設定。預設設定為 qr|^([-+@\w./]+)$|。請注意括號至關重要。

untaint_skip

如果設定,將略過未通過 untaint_pattern 的目錄,包括其所有子目錄。預設為在此情況下 die

需要的函式

wanted() 函式對每個檔案和目錄執行您想要的驗證。請注意,儘管名稱為 wanted(),但 wanted() 函式是一個通用回呼函式,並不會告訴 File::Find 檔案是否「需要」。事實上,其回傳值會被忽略。

wanted 函式不帶任何參數,而是透過變數集合執行其工作。

$File::Find::dir 是目前的目錄名稱,
$_ 是該目錄中的目前檔案名稱
$File::Find::name 是檔案的完整路徑名稱。

上述變數都已本地化,且可以在不影響 wanted 函式外部資料的情況下變更。

例如,在檢查檔案 /some/path/foo.ext 時,您會有

$File::Find::dir  = /some/path/
$_                = foo.ext
$File::Find::name = /some/path/foo.ext

在呼叫函式時,您會 chdir() 到 $File::Find::dir,除非已指定 no_chdir。請注意,當變更目錄生效時,根目錄 (/) 是個有點特殊的情況,因為 $File::Find::dir'/'$_ 的串接不等於 $File::Find::name。下表總結所有變異

             $File::Find::name  $File::Find::dir  $_
default      /                  /                 .
no_chdir=>0  /etc               /                 etc
             /etc/x             /etc              x

no_chdir=>1  /                  /                 /
             /etc               /                 /etc
             /etc/x             /etc              /etc/x

followfollow_fast 生效時,還會有 $File::Find::fullname。此函式可以設定 $File::Find::prune 以修剪樹狀結構,除非已指定 bydepth。除非已指定 followfollow_fast,為了相容性原因 (find.pl、find2perl),另外還有下列可用的全域變數:$File::Find::topdir$File::Find::topdev$File::Find::topino$File::Find::topmode$File::Find::topnlink

此函式庫對 find2perl 工具很有用 (作為 App-find2perl CPAN 套件的一部分進行散布),當提供給它時,

find2perl / -name .nfs\* -mtime +7 \
  -exec rm -f {} \; -o -fstype nfs -prune

會產生類似這樣的結果

sub wanted {
   /^\.nfs.*\z/s &&
   (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_)) &&
   int(-M _) > 7 &&
   unlink($_)
   ||
   ($nlink || (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))) &&
   $dev < 0 &&
   ($File::Find::prune = 1);
}

請注意上述 int(-M _) 中的 __ 是神奇的檔案處理程式,用於快取來自先前 stat()lstat() 或 filetest 的資訊。

以下是另一個有趣的 wanted 函式。它將找出所有無法解析的符號連結

sub wanted {
     -l && !-e && print "bogus link: $File::Find::name\n";
}

請注意,您可以在 wanted() 函數搜尋的目錄清單中,混合目錄和(非目錄)檔案。

find(\&wanted, "./foo", "./bar", "./baz/epsilon");

在上述範例中,除了 ./baz/epsilon 之外,./baz/ 中的任何檔案都不會由 wanted() 評估。

另請參閱 CPAN 上的 pfind 程式碼,以取得此模組的良好應用。

警告

如果您使用 -w 開關執行程式,或如果您使用 warnings 準則,File::Find 將針對數個奇怪的情況報告警告。您可以透過放置陳述式來停用這些警告

no warnings 'File::Find';

在適當的範圍中。請參閱 警告 以取得有關詞彙警告的更多資訊。

錯誤和注意事項

如果您確定您正在掃描的檔案系統反映了父目錄 nlink 計數中的子目錄數量,您可以將變數 $File::Find::dont_use_nlink 設定為 0。

如果您將 $File::Find::dont_use_nlink 設定為 0,您可能會注意到速度的提升,但風險是如果檔案系統未如預期填充 nlink,則不會遞迴進入子目錄。

$File::Find::dont_use_nlink 現在在所有平台上都預設為 1。

請注意,追蹤符號連結的選項可能是危險的。根據目錄樹的結構(包括到目錄的符號連結),您可能會多次穿越給定的(物理)目錄(僅在 follow_fast 生效時)。此外,刪除或變更符號連結目錄中的檔案可能會造成非常不愉快的驚喜,因為您會刪除或變更未知目錄中的檔案。

歷程

如果遞迴呼叫,File::Find 過去會產生不正確的結果。在 perl 5.8 的開發過程中,此錯誤已修正。File::Find 的第一個已修正版本為 1.01。

另請參閱

find(1)、find2perl。