為什麼 $() 中的指令印出的 {1,2} 沒有被插值?

為什麼 $() 中的指令印出的 {1,2} 沒有被插值?

我所在的目錄中有兩個文字檔:

$ touch test1.txt
$ touch test2.txt

當我嘗試使用某種模式列出文件(使用 Bash)時,它會起作用:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

但是,當由 中包含的命令產生模式時$(),只有其中一種模式有效:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

這裡發生了什麼事?為什麼該模式{1,2}不起作用?

答案1

這是兩件事的結合。首先,大括號擴充不是符合檔案名稱的模式:它是純粹的文字替換 - 請參閱`a[bc]d`(括號)和 `a{b,c}d`(大括號)有何不同?。其次,當您在雙引號 ( ) 之外使用命令替換的結果時ls $(…),發生的只是模式匹配(和分詞:“split+glob”運算符),而不是完整的重新解析。

使用 時ls $(echo 'test?.txt'),該命令echo 'test?.txt'輸出字串test?.txt(最後帶有換行符)。命令替換生成字串test?.txt(沒有最終換行符,因為命令替換會刪除尾隨換行符)。這種不帶引號的替換會進行分詞,產生一個由單一字串組成的列表,因為其中test?.txt沒有空格字元(更準確地說, 中沒有字元)。然後,該單元素列表的每個元素都會經歷條件通配符擴展,並且由於字串中$IFS存在通配符,因此確實會發生通配符擴展。?由於該模式test?.txt至少符合一個檔案名,因此列表元素將替換為與該模式相符的檔案名稱列表,從而產生包含和 的test?.txt二元素列表。最後使用兩個參數和進行呼叫。test1.txttest2.txtlstest1test2

使用 時ls $(echo 'test{1,2}'),該命令echo 'test{1,2}'輸出字串test{1,2}(最後帶有換行符)。命令替換結果為字串test{1,2}。這種不帶引號的替換會進行分詞,產生由單一字串 組成的清單test{1,2}。然後,該單元素清單中的每個元素都會經歷條件通配符擴展,這不會執行任何操作(元素保持原樣),因為字串中沒有通配符。因此ls用單一參數調用test{1,2}

為了進行比較,以下是 發生的情況ls $(echo test{1,2})。該命令echo test{1,2}輸出字串test1 test2(最後帶有換行符)。命令替換結果是字串test1 test2(沒有最後的換行符)。這種不帶引號的替換會進行分詞,產生兩個字串test1test2。然後,由於兩個字串都不包含通配符,因此它們將被單獨保留,因此ls使用兩個參數test1和進行呼叫test2

答案2

展開的順序是:大括號展開;波形符擴展、參數和變數擴展、算術擴展和命令替換(以從左到右的方式完成);分詞;和檔案名稱擴充。

命令替換後不會發生大括號擴充。您可以使用 eval 強制進行另一輪擴充:

eval echo $(echo '{1,2}lala')

結果是:

1lala 2lala

答案3

這個問題是針對 的bash,這是因為他們決定bash將大括號擴展與文件名擴展(通配符)分開,並在所有其他擴展之前首先執行它。

bash線上說明頁:

展開的順序是:大括號展開;波形符擴展、參數和變數擴展、算術擴展和命令替換(以從左到右的方式完成);分詞;和路徑名擴展。

在您的範例中,bash只有在執行命令替換($(echo ...))之後才會看到大括號,但為時已晚。

這與所有其他 shell 不同,後者在路徑名擴展(通配)之前(有些甚至作為路徑名擴展的一部分)執行大括號擴展。這包括但不限於csh括號擴展首次發明的地方。

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

csh後一個範例與、zsh、或中ksh93的相同。mkshfish

另外,請注意大括號擴展作為通配符的一部分也可以透過glob(3)函式庫函數(至少在 Linux 和所有 BSD 上)以及其他獨立實作中(例如在 perl: 中perl -le 'print join " ", <test{1,2}.txt>')來取得。

為什麼這樣做的方式不同,bash背後可能有一個故事,但FWIW我無法找到任何合乎邏輯的解釋,而且我發現所有事後合理化都無法令人信服。

答案4

請嘗試:::

ls $(回顯測試{1,2}\.txt)

附反斜線。現在可以了。還要刪除像之前的海報所說的引號。點不是用於匹配模式,而是在此處按字面意思理解為句點。

相關內容