Sup*_*per 7 command-line find xargs
尝试学习 Bash 脚本我想对当前目录下满足特定条件的所有文件执行一些命令。使用
find -name *.flac
Run Code Online (Sandbox Code Playgroud)
具体来说,我想转换.flac
为.mp3
. 我可以找到所有文件。但是,我没有看到使用-exec
forfind
和 using选项执行命令的区别xargs
。例如
find -name *.flac | xargs -i ffmpeg -i {} {}.mp3
Run Code Online (Sandbox Code Playgroud)
相比
find -name *.flac -exec ffmpeg -i {} {}.mp3 \;
Run Code Online (Sandbox Code Playgroud)
有人可以指出区别吗?什么是更好的实践?有什么优点/缺点?
另外:如果我想同时删除原始文件,我将如何在上面的代码中添加第二个命令?
Zan*_*nna 12
除非你更熟悉的xargs
不是-exec
,你可能会想使用-exec
,当您使用find
。
由于xargs
是一个单独的程序,调用它可能比 using 效率稍低-exec
,这是find
程序的一个特性。如果在可靠性、性能或可读性方面没有提供任何额外的好处,我们通常不想调用额外的程序。由于find ... -exec ...
提供了xargs
在可能的情况下使用参数列表运行命令的能力(就像那样),因此使用xargs
with find
over没有任何好处-exec
。在 的情况下ffmpeg
,我们必须指定输入和输出文件,因此我们无法使用任何一种方法来构建参数列表来提高性能,并且xargs
删除不合逻辑的原始文件扩展名更加困难。
xargs
作用注意: 中的详细标志(打印构造的命令及其参数)xargs
是-t
,交互式标志(提示用户确认对每个参数进行操作)是-p
。您可能会发现这两种方法对于理解和测试其行为都很有用。
xargs
尝试将其 STDIN(通常是已通过管道传输到它的前一个命令的 STDOUT)转换为某个命令的参数列表。
command1 | xargs command2 [output of command1 will be appended here]
Run Code Online (Sandbox Code Playgroud)
由于 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
Run Code Online (Sandbox Code Playgroud)
这会导致find
将空字符而不是空格附加到每个文件名,并且xargs
仅将空字符视为分隔符。
如果该命令不接受多个参数或参数列表非常长,则可能仍会出现问题。
在这种情况下,您使用的是ffmpeg
,它希望首先指定输入文件,最后指定输出文件。我们可以ffmpeg
用-i
标志明确地告诉哪些文件用作输入,但我们也需要给出输出文件名(通常可以猜测格式,但我们也可以指定它)。因此,要构建合适的命令,您需要使用替换字符串选项(-I
或-i
)xargs
来指定输入和输出文件:
... | xargs -I{} command {} {}.out
Run Code Online (Sandbox Code Playgroud)
(文档说出-i
于此目的已弃用,我们应该-I
改用,但我不确定为什么。使用时-I
,您必须{}
在选项后立即指定替换(通常使用)。-i
您可以省略指定替换,但{}
默认情况下会被理解。)
该-I
选项会导致命令列表仅在换行符上拆分,而不是空格,因此如果您确定文件名不包含换行符,则-print0 | xargs -0
在使用-I
. 如果您不确定,您仍然可以使用更安全的语法:
find -name "*.flac" -print0 | xargs -0I{} ffmpeg -i {} {}.mp3
Run Code Online (Sandbox Code Playgroud)
但是,xargs
(它使我们能够使用参数列表运行一次命令)的性能优势在这里丢失了,因为ffmpeg
必须为每对输入和输出文件运行一次(您可以通过预先测试echo
来轻松看到这一点)ffmpeg
以上命令)。这也会产生不合逻辑的文件名,并且不允许您运行多个命令。要执行后者,您可以调用bash
,如甜点的回答:
... | xargs -I{} bash -c 'ffmpeg -i {} {}.mp3 && rm {}'
Run Code Online (Sandbox Code Playgroud)
但重命名很棘手。
-exec
不一样当您使用-exec
选项 to 时find
,找到的文件将作为参数传递给-exec
. 它们没有变成文本。使用语法:
find ... -exec command {} \;
Run Code Online (Sandbox Code Playgroud)
command
为找到的每个文件运行一次。随着语法
find ... -exec command {} +
Run Code Online (Sandbox Code Playgroud)
一个参数列表是从找到的文件中构造出来的,这样我们就可以在多个文件上只运行一次命令(或根据需要运行多少次),从而提供xargs
. 但是,由于文件名参数不是从文本流构造的,所以 using-exec
不存在xargs
中断空格和其他特殊字符的问题。
使用ffmpeg
,我们不能+
出于同样的原因使用,因为xargs
没有提供任何性能优势;由于我们需要指定输入和输出,因此必须在每个文件上单独运行该命令。我们必须使用某种形式的
find -name "*.flac" -exec ffmpeg -i {} {}.out \;
Run Code Online (Sandbox Code Playgroud)
这将再次为您提供一个命名相当不合逻辑的文件,正如甜点的答案所解释的那样,因此您可能想要删除它,因为甜点的答案解释了如何处理字符串操作(在 中不容易完成xargs
;使用的另一个原因-exec
)。它还解释了如何对文件运行多个命令,以便您可以在成功转换后安全地删除原始文件。
而不是重复甜点的建议,这点我同意,我会建议一个替代品find
,它允许类似的灵活性运行bash -c
后-exec
; 一个bashfor
循环:
command1 | xargs command2 [output of command1 will be appended here]
Run Code Online (Sandbox Code Playgroud)
echo
测试后删除es 以对文件进行实际操作。
ffmpeg
不识别--
标记选项的结尾,因此为了避免-
以选项开头的文件名被解释为选项,我们使用./
来指示当前目录而不是以 开头**
,以便所有路径都以 开头./
而不是任意文件名。这意味着我们也不需要使用--
with rm
(它确实识别它)。
注意:如果您的-name
测试表达式包含任何通配符,您应该引用它,否则shell 会在它们被传递给之前尽可能地扩展它们(即,如果它们匹配当前目录中的任何文件)find
,因此首先,使用
find -name "*.flac"
Run Code Online (Sandbox Code Playgroud)
以防止意外行为。