為什麼 xargs 中的 !(%) 不能正常運作?

為什麼 xargs 中的 !(%) 不能正常運作?

我嘗試使用以下程式碼省略最後一個文件,同時將其餘文件複製到目標資料夾中:

ls -Art | tail -n 1 | xargs -I% cp !(%) ~/test_dst/folder1

前兩個管道獲取最新文件的名稱,並將其通過管道傳輸到 xargs 中,該 xargs 將其添加到 cp 命令中,我在其中使用 extglob 的 !通配符複製其他所有內容。但問題是,這會複製每個文件!我嘗試透過確定列印的第一部分(set -x 開啟)來調試它:

> ~/test_src/folder1$ ls -Art | tail -n 1
+ tail -n 1
+ ls --color=auto -Art
file4

成功!這是真正正確的文件!然後我嘗試使用通配符列印它:

> ~/test_src/folder1$ ls -Art | tail -n 1 | xargs -I% echo !(%)
+ tail -n 1
+ ls --color=auto -Art
+ xargs -I% echo file1 file2 file3 file4
file1 file2 file3 file4

它包括省略的文件?我嘗試自行對命令進行硬編碼,你知道嗎,它實際上工作正常:

> ~/test_src/folder1$ echo !(file4)
+ echo file1 file2 file3
file1 file2 file3

我究竟做錯了什麼?這基本上是我第一次嘗試 shell 腳本,它讓我發瘋,非常感謝任何提示。我更願意調試這個特定的方法(這只是我想到的方法),但我絕對願意接受任何建議或替代方法。謝謝你!

答案1

是的,這是行不通的。您嘗試組合的兩個功能以錯誤的順序解釋。

Extglob 是 shell(命令解釋器)的一項功能,正如您在「trace」輸出中註意到的那樣,它們由 bash 擴展實際上運行了“xargs”命令。

如果 extglobs(或任何 globs,實際上)按原樣傳遞給 xargs(或“echo”或“cp”),它將不知道如何處理它們;例如「cp」會認為「!(%)」或「!(file1)」實際上是要複製的檔案名稱。

另一方面,xargs 是不是shell 的一部分 – 它是一個像其他程式一樣的獨立程序,bash 不知道「%」的意義,也不知道您正在向 xargs 傳遞另一個命令。

所以當!(%)extglob被擴展時,它只是被擴展一次%對於整個命令 – 不是每個 xargs 輸入一次,而是Bash 在命令中看到的文字一次。這當然會導致所有未命名的文件%


一種方法是用 shell 自己的擴展替換 xargs - 與 xargs 不同,shell 變數是擴展的extglobs,所以如果你在變數中有檔名...

except_file=$(ls -Art | tail -n 1)

....你可以在 extglob 中使用它:

cp !("$except_file") ~/test_dst/folder1

(兩者可以合併為cp !("$(ls...)") ~/...。)

也可以執行相反的操作並保留 xargs,但刪除 extglob。您可以直接獲取,而不是只獲取最後一個文件tail,然後需要額外的步驟來獲取其他所有內容除了最後一個檔案使用head(“head”和“tail”的負參數表示“除了最後一個 N”):

ls -Art | head -n -1 | xargs -I% cp % ~/test_dst/folder1

或者,讓 xargs 完成cp一次為所有檔案呼叫一個的工作,假設您的系統有一個帶有以下cp -t <target>選項的 GNU Coreutils:

ls -Art | head -n -1 | xargs -d '\n' cp -t ~/test_dst/folder1

附註:Linux 上的檔案名稱可以包含換行符號。上面的許多範例都假設您沒有這樣的檔案名,因為您確實不應該 – 但它在編寫要部署在未知系統上的腳本時需要考慮一些事情,並且「防禦性」程式設計通常會涉及諸如和find ... -print0之類的工具xargs -0來使用空分隔列表,而不是換行分隔列表。 (Shell 通配符通常可以很好地處理它。)

相關內容