find与-exec和xargs有什么区别?
试图学习Bash脚本我想在当前目录下的所有满足特定条件的文件上执行一些命令。 运用
find -name *.flac
具体来说,我想将.flac
转换为.mp3
。 我可以找到所有文件。 但是,我没有看到使用-exec
选项执行命令和使用xargs
的区别。 例如
find -name *.flac | xargs -i ffmpeg -i {} {}.mp3
相比
find -name *.flac -exec ffmpeg -i {} {}.mp3 \;
有人可以指出差异吗? 什么是更好的praticice? 有什么优点/缺点?
另外:如果我想同时删除原始文件,我将如何在上面的代码中添加第二个命令?
摘要:
除非您比-exec
更熟悉xargs
,否则在使用find
时可能需要使用-exec
。
由于xargs
是一个单独的程序,因此调用它可能比使用-exec
(这是find
程序的一个function)效率-exec
。 如果在可靠性,性能或可读性方面没有提供任何额外的好处,我们通常不想要额外的程序。 由于find ... -exec ...
提供了使用参数列表运行命令的能力(如xargs
所做),如果可能的话,使用xargs
与find
over -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
,因为我们不希望将整个列表视为单个参数。 对此的标准解决方案是使用null字符作为分隔符,因为文件名不能包含它:
find path test(s) -print0 | xargs -0 command
这会导致find
将空字符附加到每个文件名而不是空格,而xargs
仅将空字符作为分隔符。
如果命令不接受多个参数或参数列表非常长,则仍可能出现问题。
在这种情况下,您使用的是ffmpeg
,它要求首先指定输入文件,最后指定输出文件。 我们可以使用-i
标志告诉ffmpeg
哪些文件作为输入显式,但是我们需要给出输出文件名(通常猜测格式,尽管我们也可以指定它)。 因此,要构造合适的命令,您需要使用xargs
的替换字符串选项( -I
或-i
)来指定输入和输出文件:
... | xargs -I{} command {} {}. out
(文档说为了这个目的不推荐使用-I
,我们应该使用-I
代替,但我不确定原因。使用-I
,必须在选项后立即指定替换( {}
通常使用)。 -i
您可以省略指定替换,但默认情况下会理解{}
。)
-I
选项导致命令列表仅在换行符而不是空格中拆分,因此如果您确定文件名不包含换行符,则不必使用-print0 | xargs -0
使用-I
时-print0 | xargs -0
。 如果您不确定,仍然可以使用更安全的语法:
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
没有给出任何性能优势; 因为我们需要同时指定输入和输出,所以必须分别对每个文件运行命令。 我们必须使用某种forms
find -name "*.flac" -exec ffmpeg -i {} {}.out \;
再次,这将给你一个相当不合逻辑的命名文件,正如甜点的答案所解释的那样 ,你可能想要剥离它,因为甜点的答案解释了如何处理字符串操作(在xargs
不容易做到;使用-exec
另一个原因) 。 它还说明了如何在文件上运行多个命令,以便在成功转换后可以安全地删除原始文件。
我不同意重复甜点的建议,我会建议另一种方法,它允许在-exec
之后运行bash -c
类似灵活性; bash 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
无法识别--
为了标记选项的结尾,所以为了避免以-
开头的文件名被解释为选项,我们使用./
来指示当前目录而不是以**
开头,以便所有路径都以./
开头。而不是任意文件名。 这意味着我们不需要使用--
使用rm
(它确实识别它)。
注意 :如果你的-name
测试表达式包含任何通配符,你应该引用它们,否则如果可能的话shell会扩展它们(即如果它们匹配当前目录中的任何文件),那么在它们传递给find
,所以首先,使用
find -name "*.flac"
防止意外行为。
通常会尝试尽可能少地调用命令,但在您的情况下,我认为这是一个品味问题 – 我会使用-exec
,使用它如下:
find . -name '*.flac' -exec bash -c 'ffmpeg -i "$0" "${0%flac}mp3" && rm "$0"' {} \;
诀窍是使用-c
选项调用bash
,这样你不仅可以执行多个命令,还可以使用Bash参数替换从文件名中删除flac
结尾 – 我想你真的不想最终得到名为的文件filename.flac.mp3 ,对吗?
说明
-
bash -c '…' {}
– 以bash
运行命令…
文件名作为第一个参数(可以用$0
访问) -
${0%flac}
– 从文件名末尾剥离flac
-
&& rm "$0"
– 仅当前面的命令成功时,删除原始文件
由于Zanna和甜点已经回答-exec
应该首选xargs
不是必需的( “如果它在可靠性,性能或可读性方面没有提供任何额外的好处,我们通常不想调用额外的程序。” )
虽然这是完全正确的,但我想补充一点, xargs
与-P
标志相结合可以在性能方面提供实质性的好处。
xargs
将并行生成进程,启用multithreading,类似于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. [...]
这特别有助于处理不自行运行multithreading的进程。 在你的情况下, ffmpeg
将关注multithreading,因此它对性能没有帮助或甚至会产生负面影响。
find . -name "*.ext" -print0 | xargs -0 -i -P 20 command -in {} -out {}.out