HOWTO · Bash

在 Bash 中執行 find -exec 命令

本文將示範如何使用 find 的 -exec 動作,將 find 選取的檔案路徑傳給其他命令;若要搜尋檔案內容,請使用 grep。

本頁內容

find 會選取檔案系統路徑;grep 會搜尋檔案內容。-exec 動作會將選取到的每個路徑作為引數傳給另一個命令,串接這兩項工作。本指南介紹可重複使用的語法、內容搜尋方式、逐筆執行與批次傳入路徑的差異,以及謹慎執行檔案操作的方法。

find -exec 的運作方式

find 會從你指定的目錄開始,檢查檔案類型、名稱、時間或大小等條件,並逐一走訪符合條件的路徑。搭配 -exec 時,它會呼叫你指定的命令,並以每個符合條件的路徑取代 {}。路徑會作為引數傳入,因此檔名中的空格、定位字元、引號和萬用字元不會再由 shell 重新解析。這比組出命令字串,或將以換行分隔的名稱傳給另一個命令更安全。

搜尋與動作是兩項不同工作:-name '*.txt' 這類條件會選取檔案,而 grep 這類命令則會讀取檔案內容。如果動作只應套用到一般檔案,請加上 -type f。find 預設也會處理目錄,因此請選擇符合命令預期輸入類型的條件。

find -exec 的基本語法

可重複使用的形式如下:find STARTING_PATH TESTS -exec COMMAND {} \;。STARTING_PATH 指定要搜尋的目錄樹;條件會篩選可交給動作處理的路徑;COMMAND 及其選項指定要執行的工作;{} 則標示 find 插入路徑的位置。在互動式 shell 中,結尾分隔符要寫成 \;,讓 shell 將字面上的分號傳給 find。若未跳脫或加上引號,shell 會把分號當作命令分隔符。

請將 '*.txt' 這類檔名樣式加上引號。引號會讓 shell 原樣傳遞樣式,讓 find 在每個走訪到的路徑中比對名稱,而不是依照目前目錄中的檔案展開樣式。若路徑以連字號開頭,請使用明確的起始目錄,例如 ./-archive,避免它被誤認為命令選項。

執行以下範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

set -eu
root=$(mktemp -d)
trap 'rm -rf "$root"' EXIT
mkdir -p "$root/notes"
printf 'report: first\nkeep this line\n' > "$root/notes/weekly report.txt"
printf 'draft only\n' > "$root/notes/draft.txt"
find "$root" -type f -name '*.txt' -exec grep -nH 'report' {} +

此範例會建立自己的小型目錄,只搜尋文字檔,並讓 grep 印出符合項目的行號與檔名。即使某次呼叫只收到一個路徑,-H 仍會顯示檔名。shell 結束時會移除暫存目錄。這是一個可執行的示範;此處不宣稱有任何擷取的輸出結果。

使用 find 和 grep 搜尋檔案內容

若要搜尋 /documents 下的文字,請使用 find /documents -type f -name '*.txt' -exec grep -nH 'report' {} +。find 會選取可能符合條件的文字檔,而 grep 會印出每一個符合的行,以及其路徑與行號。如果只需要列出包含符合內容的檔名,可用 grep -l 取代 -nH。同一個檔案可能有多行符合,因此逐行輸出的形式可能會重複列出同一路徑。

搜尋字串有加上引號,所以會作為單一引數原樣傳入。使用正規表示式時,請仔細指定運算式;若要比對固定字串,grep -F 可避免將正規表示式中繼字元解讀為特殊符號。文字搜尋可能因權限或檔案編碼而無法讀取檔案;若要在腳本中依搜尋結果採取動作,也要將命令的診斷訊息和結束狀態納入考量。此處不列出範例路徑,因為實際符合項目取決於讀者目錄中的檔案。

如何選擇 ; 或 +

使用 -exec COMMAND {} \; 時,find 會針對每個符合的路徑分別執行命令。這種形式容易理解,也適合一次處理一個路徑的命令。經 shell 跳脫的分號會結束動作定義;它不是要執行的命令內容。

使用 -exec COMMAND {} + 時,find 會在每次呼叫命令時提供多個符合的路徑,並將它們分成符合系統引數大小限制的群組。請只放一次 {},並將它放在 + 前的最後一個命令引數。對 grep 這類工具而言,批次處理通常能減少啟動程序的額外負擔,但不會改變這些工具對各檔案執行的工作;若命令的用途取決於一次只收到一個路徑,批次處理也不適用。加號會直接傳給 find,不需要在 shell 中跳脫。

安全地對符合條件的檔案執行其他命令

刪除舊記錄檔前,先將動作改成 -print,檢查列出的路徑:find /var/log -type f -name '*.log' -mtime +30 -print。-mtime +30 條件會選取修改時間距今超過 30 個完整 24 小時的檔案;它不是指「比本月三十日更早」。確認選取結果並具備所需權限後,GNU 與許多其他 find 實作支援 -delete,例如 find /var/log -type f -name '*.log' -mtime +30 -delete。此命令執行的刪除無法復原,因此使用前請確認起始目錄和篩選條件。

若要將下載目錄中大於 100 mebibytes 的一般檔案移走,請先檢查 find /downloads -type f -size +100M -print。確認清單無誤,且 /archive/ 位於同一系統並具有適當權限後,可逐檔執行:find /downloads -type f -size +100M -exec mv -- {} /archive/ \;。+100M 大小條件使用 find 的大小單位,並向上取整到下一個單位;若需要精確的位元組門檻,請先確認該實作的單位慣例。移動檔案可能覆寫目的地中已有的同名檔案,或造成名稱衝突,因此執行前請檢查是否有重複的基本檔名。無論是哪一項工作,都應使用明確的篩選條件,並在變更檔案前檢查預覽結果。