sh 語法來處理與通配符相符的零個文件,以及更多?

sh 語法來處理與通配符相符的零個文件,以及更多?

我想編寫一個/bin/shshell 腳本來處理與通配符匹配的任何檔案。處理 1 個或多個符合檔案很容易。但是,我發現處理 0 個符合檔案的情況很尷尬。

明顯的構造是:

#!/bin/sh
for f in *.ext; do
  handle "$f"
done

其中*.ext可以是 shell 與檔案路徑進行比較的一個或多個表達式,並且handle是一個shell 命令,如果給定現有檔案的路徑,則該命令可以正確運行,但如果給出未對應到檔案的路徑,則失敗。 (在引發這個問題的情況下,它們是*.flacffmpeg,但我認為這並不重要。)

如果存在符合的檔案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)及其空球選項。謝謝。由於他們沒有立即通過完整的示例提供答案,因此我自己這樣做。

相關內容