尝试学习 Bash 脚本 我想对当前目录下满足特定条件的所有文件执行一些命令。使用
find -name *.flac
具体来说,我想转换.flac
为.mp3
。我可以找到所有文件。但是,我看不出使用选项和使用执行命令有什么-exec
区别find
。xargs
例如
find -name *.flac | xargs -i ffmpeg -i {} {}.mp3
相比
find -name *.flac -exec ffmpeg -i {} {}.mp3 \;
有人能指出区别吗?有什么更好的做法?优点/缺点是什么?
另外:如果我想同时删除原始文件,如何在上面的代码中添加第二个命令?
答案1
概括:
xargs
除非您对比更熟悉-exec
,否则您可能希望-exec
在使用时使用find
。
由于xargs
是一个单独的程序,调用它可能比使用 (-exec
这是程序的一个功能)效率略低find
。如果调用一个额外的程序在可靠性、性能或可读性方面没有提供任何额外的好处,我们通常不想这样做。由于find ... -exec ...
提供了使用参数列表运行命令的能力(就像xargs
一样),因此使用xargs
相对于find
并没有什么优势-exec
。在 的情况下ffmpeg
,我们必须指定输入和输出文件,因此我们无法使用任何一种方法来构造参数列表来提高性能,并且xargs
删除不合逻辑的原始文件扩展名更加困难。
什么xargs
是
笔记:中的详细标志(打印构造的命令及其参数)xargs
是-t
,而交互标志(提示用户确认是否对每个参数进行操作)是-p
。您可能会发现这两者都有助于理解和测试其行为。
xargs
尝试将其 STDIN(通常是通过管道传输给它的前一个命令的 STDOUT)转换为某个命令的参数列表。
command1 | xargs command2 [output of command1 will be appended here]
由于 STDOUT 或 STDIN 只是文本流(这也是您不应该解析 的输出的原因ls
),xargs
因此很容易出错。它将参数读取为由空格或换行符分隔。文件名可以包含空格,甚至可以包含换行符,这样的文件名会导致意外行为。假设您有一个名为 的文件foo bar
。当包含此文件名的列表通过管道传输到 时,它会尝试一次又一次地xargs
运行给定的命令。foo
bar
当您输入 时,也会出现同样的问题command foo bar
,并且您知道可以通过引用空格或整个名称来避免此问题,例如command foo\ bar
或command "foo bar"
,但即使我们能够引用传递给 的列表,xargs
我们通常也不想这样做,因为我们不希望将整个列表视为单个参数。此问题的标准解决方案是使用空字符作为分隔符,因为文件名不能包含它:
find path test(s) -print0 | xargs -0 command
这会导致find
将空字符而不是空格附加到每个文件名,并且xargs
仅将空字符视为分隔符。
如果命令不接受多个参数或者参数列表非常长,仍然可能出现问题。
在这种情况下,您使用的是ffmpeg
,它要求首先指定输入文件,最后指定输出文件。我们可以使用ffmpeg
标志明确指定要使用哪些文件作为输入-i
,但我们需要提供输出文件名(通常从中猜测格式,尽管我们也可以指定它)。因此,要构造合适的命令,您需要使用 的替换字符串选项(-I
或-i
)来xargs
指定输入和输出文件:
... | xargs -I{} command {} {}.out
(文档说-i
为此目的不推荐使用,我们应该使用-I
,但我不知道为什么。使用时,您必须在选项后立即-I
指定替换(通常使用)。使用时,您可以省略指定替换,但默认情况下是可以理解的。){}
-i
{}
该选项使命令列表仅在换行符上拆分,而不是空格,因此如果您确定文件名不包含换行符,则在使用时-I
不必使用。如果您不确定,您仍然可以使用更安全的语法:-print0 | xargs -0
-I
find -name "*.flac" -print0 | xargs -0I{} ffmpeg -i {} {}.mp3
但是, 的性能优势xargs
(它使我们能够使用参数列表运行一次命令)在这里丢失了,因为ffmpeg
必须对每对输入和输出文件运行一次(您可以通过在前面添加echo
来ffmpeg
测试上述命令来轻松看到这一点)。这还会产生不合逻辑的文件名,并且不允许您运行多个命令。要执行后者,您可以调用bash
,如下所示甜点的答案:
... | xargs -I{} bash -c 'ffmpeg -i {} {}.mp3 && rm {}'
但重命名很棘手。
有何-exec
不同
当您使用该-exec
选项时find
,找到的文件将作为参数到 之后的命令-exec
。它们不会变成文本。语法如下:
find ... -exec command {} \;
command
对找到的每个文件运行一次。语法
find ... -exec command {} +
参数列表是根据找到的文件构建的,这样我们就可以对多个文件只运行一次命令(或根据需要运行多次),从而获得 提供的性能优势xargs
。但是,由于文件名参数不是从文本流构建的,因此使用-exec
不会xargs
出现空格和其他特殊字符中断的问题。
对于ffmpeg
,我们不能使用 ,+
原因与 相同,因为xargs
不会带来任何性能优势;因为我们需要指定输入和输出,所以必须对每个文件分别运行该命令。我们必须使用某种形式的
find -name "*.flac" -exec ffmpeg -i {} {}.out \;
这又会给你一个不合逻辑的文件名称,因为dessert 的答案解释道,因此您可能想要删除它,因为 dessert 的答案解释了如何处理字符串操作(在 中不容易完成xargs
;使用 的另一个原因-exec
)。它还解释了如何在文件上运行多个命令,以便您可以在成功转换后安全地删除原始文件。
我不会重复 dessert 的建议(我同意),而是建议一种替代方案,它提供了与在bash循环后find
运行类似的灵活性:bash -c
-exec
for
shopt -s globstar # allow recursive globbing with **
for f in ./**/*.flac; do # for all files ending with .flac
# convert them, stripping the original extension from the new filename
echo ffmpeg -i "$f" "${f%.flac}.mp3" &&
echo rm -v "$f" # if that succeeded, delete the original
done
shopt -u globstar # turn recursive globbing off
测试完毕后去掉echo
es就可以对文件进行实际操作了。
ffmpeg
无法识别--
标记选项的结尾,因此为了避免以 开头的文件名-
被解释为选项,我们使用./
来指示当前目录,而不是以 开头**
,以便所有路径都以 开头,./
而不是任意文件名。这意味着我们也不需要使用--
with rm
(它可以识别它)。
笔记:-name
如果测试表达式包含任何通配符,则应将其括起来,否则 shell 会在将其传递给之前尽可能地扩展它们(即,如果它们与当前目录中的任何文件匹配)find
,因此首先,使用
find -name "*.flac"
以防止意外行为。
答案2
一般来说,人们会尝试调用尽可能少的命令,但在你的情况下,我认为这只是个人喜好问题——我会选择-exec
,像这样使用它:
find . -name '*.flac' -exec bash -c 'ffmpeg -i "$0" "${0%flac}mp3" && rm "$0"' {} \;
诀窍是使用选项调用bash
,-c
这样您不仅可以执行多个命令,还可以使用Bash 参数替换删除flac
文件名的结尾——我想你肯定不希望文件名变成文件名.flac.mp3, 你?
解释
bash -c '…' {}
– 运行命令,…
以bash
文件名作为第一个参数(可通过 访问$0
)${0%flac}
–flac
从文件名末尾删除&& rm "$0"
– 仅当上述命令成功执行时,才删除原始文件
答案3
正如 Zanna 和 dessert 已经回答的那样,在没有必要的-exec
情况下应该优先考虑(xargs
“如果一个额外的程序在可靠性、性能或可读性方面不能提供任何额外的好处,我们通常不想调用它。”)
虽然这是完全正确的,但我想补充一点,xargs
与-P
国旗结合起来,可以带来实质性的好处,表现。
xargs
将产生并行进程,从而实现多线程,与命令类似但更灵活parallel
。
-P max-procs, --max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option or the -L option with -P; other‐
wise chances are that only one exec will be done.
[...]
这对于那些本身不运行多线程的进程尤其有用。 在您的案例中,ffmpeg
将关注多线程,因此它不会有帮助,甚至会对性能产生负面影响。
find . -name "*.ext" -print0 | xargs -0 -i -P 20 command -in {} -out {}.out