为什么 xargs 中的 !(%) 不能正常工作?

为什么 xargs 中的 !(%) 不能正常工作?

我尝试使用以下代码来省略最后一个文件,同时将其余文件复制到目标文件夹:

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

前两个管道获取最新文件的名称,并将其通过管道传输到 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

是的,这行不通。您尝试组合的两个功能的解释顺序错误。

Extglobs 是 shell(命令解释器)的一个特性,正如你在“trace”输出中注意到的,它们被 bash 扩展了'xargs' 命令实际上已经运行。

如果将 extglobs(或者任何 glob)按原样传递给 xargs(或“echo”或“cp”),它将不知道如何处理它们;例如,“cp”会认为“!(%)”或“!(file1)”实际上是需要复制的文件名。

另一方面,xargs 是不是作为 shell 的一部分 – 它和其他程序一样都是独立程序,bash 不知道“%”的含义,也不知道您正在将另一个命令传递给 xargs。

因此,当!(%)扩展 extglob 时,它只会扩展一次%对于整个命令 – 不是对每个 xargs 输入执行一次,而是对Bash 在命令中看到的文字执行一次。这当然会导致所有未命名的文件%


一种方法是将 xargs 替换为 shell 自己的扩展 - 与 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对所有文件同时调用一个命令的工作,假设你的系统有 GNU Coreutils 并且带有以下cp -t <target>选项:

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

附注:Linux 上的文件名可以包含换行符。上面的许多示例都假设您没有这样的文件名,因为您确实不应该有这样的文件名——但它在编写部署到未知系统的脚本时需要考虑这一点,“防御性”编程通常会涉及和等工具find ... -print0xargs -0处理以空值分隔的列表,而不是以换行符分隔的列表。(Shell 通配符通常可以很好地处理它。)

相关内容