该答案分为以下几部分:
- 的基本用法
-exec
- 使用
-exec结合sh -c
- 使用
-exec ... {} +
- 使用
-execdir
的基本用法 -exec
该-exec选项采用带有可选参数作为参数的外部实用程序并执行它。
如果该字符串{}出现在给定命令的任何位置,则该字符串的每个实例都将被当前正在处理的路径名替换(例如./some/path/FILENAME)。在大多数shell中,两个字符{}都不需要用引号引起来。
该命令需要与被终止;为find知道其在此结束(因为可能有进一步的选项之后)。为了保护;外壳程序,请用\;或引起引用';',否则外壳程序会将其视为find命令的结尾。
示例(\前两行的末尾仅用于连续行):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
这将找到-type f名称与*.txt当前目录中或下方的模式匹配的所有常规文件()。然后它将使用来测试字符串是否hello出现在找到的任何文件中grep -q(不会产生任何输出,只是退出状态)。对于那些包含字符串的文件,cat将被执行以将文件的内容输出到终端。
每个对象-exec也find像-type和一样,-name对通过找到的路径名进行“测试” 。如果命令返回退出状态为零(表示“成功”),find则考虑命令的下一部分,否则find命令以下一个路径名继续。在上面的示例中,这用于查找包含字符串的文件hello,但忽略所有其他文件。
上面的示例说明了以下两个最常见的用例-exec:
- 作为测试,进一步限制了搜索。
- 对找到的路径名执行某种操作(通常,但不一定在
find命令末尾)。
使用-exec结合sh -c
-exec可以执行的命令仅限于带有可选参数的外部实用程序。-exec不能直接使用shell的内置函数,条件,条件,管道,重定向等,除非将其包装在诸如sh -c子shell之类的东西中。
如果bash需要功能,请使用bash -c代替sh -c。
sh -c/bin/sh使用命令行上给定的脚本运行,然后是该脚本的可选命令行参数。
一个单独使用sh -c的简单示例,不包含find:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
这会将两个参数传递给子shell脚本:
字符串sh。这将$0在脚本内部提供,并且如果内部外壳输出错误消息,它将在此字符串之前添加前缀。
该参数apples可以$1在脚本中使用,并且有更多参数,则这些参数可以在中使用$2,$3等等。它们在列表中也可以使用"$@"(除非$0不是的一部分"$@")。
与之结合使用时非常有用,-exec因为它使我们能够制作任意复杂的脚本,这些脚本对由所找到的路径名起作用find。
示例:查找所有具有特定文件名后缀的常规文件,并将该文件名后缀更改为其他后缀,其中后缀保留在变量中:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
在内部脚本中,$1将是string text,$2将是字符串txt,$3并将是find为我们找到的任何路径名。参数扩展${3%.$1}将采用路径名并.text从中删除后缀。
或者,使用dirname/ basename:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
或者,在内部脚本中添加变量:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
请注意,在最后一个变体中,变量from和to子外壳中的变量与外部脚本中具有相同名称的变量不同。
以上是从调用任意复杂的脚本的正确方法-exec用find。find像这样循环使用
for pathname in $( find ... ); do
容易出错且不雅(个人意见)。它在空格上分割文件名,调用文件名find遍历,并且甚至在运行循环的第一次迭代之前,还强制外壳扩展完整的结果。
也可以看看:
使用 -exec ... {} +
所述;在端部可以通过替换+。这将导致find使用尽可能多的参数(找到的路径名)执行给定命令,而不是为每个找到的路径名执行一次。 该字符串{} 必须恰好在之前出现+,才能起作用。
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
在这里,find将收集生成的路径名并cat一次在尽可能多的路径名上执行。
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
同样在这里,mv将执行最少的次数。最后一个示例需要mvcoreutils(支持该-t选项)中的GNU 。
使用-exec sh -c ... {} +也是一种用任意复杂的脚本遍历一组路径名的有效方法。
基本知识与使用时相同-exec sh -c ... {} ';',但是脚本现在需要更长的参数列表。这些可以通过"$@"在脚本内部循环来循环。
上一节中的示例更改了文件名后缀:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
使用 -execdir
也有-execdir(由大多数find变体实现,但不是标准选项)。
这样做-exec的区别在于,给定的shell命令以找到的路径名的目录作为当前工作目录执行,并且{}将包含找到的路径名的基名(不带路径)(但是BNU find仍会以开头为基名./,而BSD find不会这样做)。
例:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
这会将每个找到的*.txt文件移动到与找到文件所在目录相同的目录中的done-texts子目录中。该文件也将通过添加后缀.done来重命名。
这将有些棘手,-exec因为我们必须从中找到找到的文件的基本名称,{}以形成文件的新名称。我们还需要from的目录名{}才能done-texts正确找到目录。
使用-execdir,类似这样的事情会变得更容易。
使用-exec代替的相应操作-execdir必须采用子外壳程序:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
要么,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +