该答案分为以下几部分:
- 的基本用法
-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
将执行最少的次数。最后一个示例需要mv
coreutils(支持该-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 {} +