打開和創建文件時的 Bash 擴展不對稱
我遇到了以下 Bash 正則表達式中的不對稱範例,這讓我感到困惑。我想知道我在做什麼是非標準的並導致這種行為,或者我錯過的這種行為背後的邏輯是什麼。
打開文件
假設我有一個目錄,其中包含一個名為
file1.txt
.file20.txt
我想在我最喜歡的文本編輯器中打開所有這些。為此,在某種意義上,Bash 必須“讀取”目錄的內容並將它們傳遞給 Vim。我可以使用以下正則表達式來做到這一點:vim file{[1-9],1[0-9],20}.txt
這行得通。執行此命令後,Vim 打開,在緩衝區列表中我可以看到所有文件
file1.txt
到file20.txt
.創建文件
現在假設我們處於不同的場景:我們從一個空目錄開始,我們想要創建文件
file1.txt
到file20.txt
. 為此,從某種意義上說,Bash 必須將文件名“寫入”到目錄中。不幸的是,在這種情況下,前面的命令不起作用。我沒有創建所需的 20 個文件,而是在緩衝區列表中得到以下文件:file[1-9].txt file[0-9].txt file20.txt
因此,不是將方括號解釋
[]
為正則表達式的一部分,而是將它們合併到名稱中。為什麼在閱讀與寫作時會發生這種不對稱,我以後如何避免這種情況?
您使用的不是正則表達式,而是大括號擴展和文件名擴展(又名萬用字元)的組合。這很重要,因為雖然大括號擴展只是將包含
{ ... }
構造的字元串擴展為幾個不同的字元串,但通配部分實際上試圖將現有文件與模式匹配。這就是問題所在(順便說一句,即使是正則表達式也用於將現有字元串與模式匹配,而不是根據模式生成字元串)。特別要注意,大括號擴展是在文件名擴展之前執行的。
所以
file{[1-9],1[0-9],20}.txt
由 shell 擴展為三個以空格分隔的標記
file[1-9].txt file1[0-9].txt file20.txt
然後受到實際文件名擴展的影響,shell 檢查哪些現有文件與該 glob 模式匹配。重要的部分是,如果沒有文件與其中一種模式匹配,則模式將按字面意思進行。
所以在你開場的情況下,會發生什麼
vim file{[1-9],1[0-9],20}.txt
擴展為vim file[1-9].txt file1[0-9].txt file20.txt
vim file[1-9].txt file1[0-9].txt file20.txt
擴展為vim file1.txt file2.txt ... file20.txt
因為所有這些文件都存在(它不會擴展為該數字範圍內的任何不存在的文件)vim
打開所有這些文件。但是,當使用 eg
touch
與相同的參數來創建不存在的文件時,會發生什麼
touch file{[1-9],1[0-9],20}.txt
擴展為touch file[1-9].txt file1[0-9].txt file20.txt
- 由於沒有文件與該模式匹配,因此
[1-9]
,1[0-9]
和20
保持字面意思touch
用字面意思的名稱創建這三個文件。如果您想避免這種情況,並且由於您想創建該範圍內的所有文件,您可以簡單地將命令行限制為大括號擴展,即
touch file{1..20}.txt
(正如 pLumo 在評論中所指出的)
作為旁注(@Quasimodo 建議),在
bash
和許多其他 shell 中,通配行為可以通過shell options調整,bash
特別是使用.shopt -s *option*
在這裡,該
nullglob
選項特別有趣,因為它將使 shell 擴展一個不匹配任何文件名的通配模式到空字元串,而不是按字面意思將模式保留在其中。如果您想使用循環遍歷與模式匹配的所有文件,這將特別有用for
:
- 沒有
nullglob
選項,表單的 循環for f in *.txt
如果目前目錄中不存在, 將只執行一次並
$f
設置為文字 ,這可能導致意外行為(即程式碼試圖對不存在的文件進行操作)*.txt``.txt
- 使用該
nullglob
選項,shell 根本不會進入循環體。另一方面(正如@Barmar 正確指出的那樣),
stdin
如果您為它們提供一個評估為“無”的 glob 模式,許多對文件進行操作的程序將默默地嘗試讀取,因為沒有文件名匹配,因此使用此選項可以如果你不小心,奇怪的副作用。此外
nullglob
,Bash 有一個failglob
選項,如果有一個不匹配任何內容的 glob,它將給出錯誤而不是執行命令。