我发现自己不断查找语法
find . -name "FILENAME" -exec rm {} \;
主要是因为我不明白该-exec
部分到底是如何工作的。大括号、反斜杠和分号的含义是什么?该语法还有其他用例吗?
答案1
这个答案分为以下几个部分:
- 基本用法
-exec
-exec
结合使用sh -c
- 使用
-exec ... {} +
- 使用
-execdir
基本用法-exec
该-exec
选项采用带有可选参数的外部实用程序作为其参数并执行它。
如果该字符串{}
出现在给定命令中的任何位置,则该字符串的每个实例都将替换为当前正在处理的路径名(例如./some/path/FILENAME
)。在大多数 shell 中,这两个字符{}
不需要加引号。
该命令需要以;
for结尾find
才能知道它在哪里结束(因为之后可能还有更多选项)。为了保护;
免受 shell 的影响,需要将其引用为\;
或';'
,否则 shell 会将其视为命令的结尾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
仅限于带有可选参数的外部实用程序。直接使用 shell 内置函数、函数、条件、管道、重定向等-exec
是不可能的,除非包装在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 脚本。这些将被放置在脚本中$0
并$1
供脚本使用。
字符串
sh
。这将在脚本内部可用$0
,如果内部 shell 输出错误消息,它将使用此字符串作为前缀。该参数在脚本中
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
将是 string 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" {} ';'
请注意,在最后一个变体中,子 shell 中的变量from
和to
与外部脚本中同名的变量不同。
以上是从-exec
with调用任意复杂脚本的正确方法find
。find
在循环中使用,例如
for pathname in $( find ... ); do
容易出错且不优雅(个人意见)。它在空格上分割文件名,调用文件名通配符,并且还强制 shellfind
在运行循环的第一次迭代之前扩展完整结果。
也可以看看:
使用-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 中的 GNU(支持该-t
选项)。
使用-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 命令以找到的路径名的目录作为其当前工作目录执行,并且{}
将包含找到的路径名的基本名称而不包含其路径(但 GNUfind
仍将在基本名称前加上 前缀./
,而 BSDfind
或sfind
不会)。
例子:
find . -type f -name '*.txt' \
-execdir mv -- {} 'done-texts/{}.done' \;
这会将每个找到的*.txt
文件移动到预先存在的done-texts
子目录中在与找到该文件的目录相同的目录中。该文件还将通过添加后缀.done
来重命名。 ,在那些不以 . 为基本名称前缀的实现--
中,需要标记选项的结尾。如果您的 shell 是 ,则需要将包含 not 的参数作为整体括起来。另请注意,并非所有实现都会在那里扩展(不会)。find
./
{}
(t)csh
find
{}
sfind
这会有点棘手,-exec
因为我们必须获取找到的文件的基本名称来{}
形成文件的新名称。我们还需要目录名称来正确{}
定位该done-texts
目录。
有了-execdir
,一些事情就变得更容易了。
-exec
使用相反 的相应操作-execdir
必须使用子 shell:
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 {} +