我想編寫一個/bin/sh
shell 腳本來處理與通配符匹配的任何檔案。處理 1 個或多個符合檔案很容易。但是,我發現處理 0 個符合檔案的情況很尷尬。
明顯的構造是:
#!/bin/sh
for f in *.ext; do
handle "$f"
done
其中*.ext
可以是 shell 與檔案路徑進行比較的一個或多個表達式,並且handle
是一個shell 命令,如果給定現有檔案的路徑,則該命令可以正確運行,但如果給出未對應到檔案的路徑,則失敗。 (在引發這個問題的情況下,它們是*.flac
和ffmpeg
,但我認為這並不重要。)
如果存在符合的檔案foo.ext
, bar.ext
,則此腳本執行
handle "foo.ext"
handle "bar.ext"
正如預期的那樣。然而,如果有不是任何匹配的文件,此腳本都會給出錯誤訊息,例如,
handle: *.ext: No such file or directory
我想我明白為什麼會發生這種情況:巴什手冊頁(我認為這也有效/bin/sh
)說「下面的單字列表in
被擴展,產生一個項目列表......如果後面的項目的擴展導致一個空列表,則不會執行任何命令,並且返回狀態是0。 」顯然,當*.ext
「展開」時,結果清單包括*.ext
,而不是空列表。但這並不能解釋如何阻止這種情況發生。
(更新:有一個sh (1)
Heirloom 專案的手冊頁它描述的是 Bourne Shell sh
,而不是後來的 Bourne Again Shell bash
。在下面檔案名稱生成,它清楚地表明,“如果找不到與模式匹配的文件名,則該單字保持不變。”它解釋了為什麼在列表中sh
留下模式。*.ext
編寫此循環的緊湊、慣用方法是什麼,以便在沒有匹配文件的情況下運行零次而不會出現錯誤,但為每個匹配文件運行一次?更好的是,什麼可以適用於多種模式,例如:
for f in *.ext1 special.ext1 *.ext2; do ...
/bin/sh
為了可移植性,我更喜歡使用與 , 相容的語法。我碰巧使用的是 Mac OS X 10.11.6,但我希望語法可以在任何類 Unix 作業系統上運行。
我想出了一些笨拙的、非慣用的方法來寫這樣的循環。如果我沒有立即得到好的答案,我會將其作為答案提供,以供記錄。
答案1
最簡單的可移植方法是,如果擴展沒有產生實際存在的東西,則跳過循環:
for f in *.ext1 *.ext2; do
[ -e "$f" ] || continue
handle "$f"
done
說明:假設有多個 .ext2 文件,但沒有 .ext1 文件。在這種情況下,通配符將擴展為*.ext1
filea.ext2
fileb.ext2
filec.ext2
.因此[ -e "*.ext1" ]
失敗並運行continue
,這會跳過循環迭代的其餘部分。然後[ -e "filea.ext2" ]
等成功,並且循環的這些迭代正常運行。
順便說一句,您可以將其修改為例如[ -f "$f" ] || continue
跳過任何不是普通文件的內容(如果您想跳過目錄等)。
當然,如果您在 bash 下運行,則可以先運行,shopt -s nullglob
這樣就不會在列表中留下不匹配的模式。然後shopt -u nullglob
防止意外的副作用(例如,如果設定了,grep pattern *.nonexistent
將嘗試從標準輸入讀取)。nullglob
答案2
/bin/bash
從 Bourne Shell ( ) 切換到 Bourne Again Shell ( /bin/sh
),一個簡單的解決方案就成為可能。
這bash(1)
手冊頁提到了空球選項:
如果設置,bash 允許不匹配任何文件的模式(參見路徑名擴充上面)擴展為空字串,而不是它們本身。
這路徑名擴充部分說:
分詞後,…bash 掃描每個單字中的字符*,?, 和[。如果出現這些字元之一,則該單字被視為一種模式,並替換為與該模式相符的按字母順序排序的檔案名稱清單。如果沒有找到符合的檔案名,則 shell 選項空球未啟用,該詞保持不變。如果空球設定選項,但未找到匹配項,該單字將被刪除。
因此,設定空球選項給出了模式所需的行為。如果沒有文件與該模式匹配,則循環不會執行。
#!/bin/bash
shopt -s nullglob
for f in *.ext; do
handle "$f"
done
但是空球選項很可能會幹擾其他指令的所需行為。因此,您可能會發現保存現有設定是明智的空球,然後恢復它。幸運的是,shopt -p
內建指令以可重複用作輸入的形式發出輸出。但它會發出所有選項的輸出,因此請使用grep
來挑選空對象環境。請參閱下面的日誌(其中$
表示 bash 命令提示字元):
$ shopt -p | grep nullglob
shopt -u nullglob
$ shopt -s nullglob
$ shopt -p | grep nullglob
shopt -s nullglob
因此,處理零個或多個與通配符模式相符的檔案的最終 bash 語法如下所示:
#!/bin/bash
SAVED_NULLGLOB=$(shopt -p | grep nullglob)
shopt -s nullglob
for f in *.ext; do
handle "$f"
done
eval "$SAVED_NULLGLOB"
此外,該問題提到了多種模式,例如:
for f in *.ext1 special.ext1 *.ext2; do ...
這空球選項不會影響列表中不是“模式”的單詞,因此special.ext1
仍然會傳遞到循環。唯一的解決方案是@戈登戴維森的continue
表情,[ -e "$f" ] || continue
.
致謝:兩者@伊格納西奧巴斯克斯-艾布拉姆斯和@戈登戴維森提到bash(1)
及其空球選項。謝謝。由於他們沒有立即通過完整的示例提供答案,因此我自己這樣做。