我发现自己不断地查找语法
find . -name "FILENAME"  -exec rm {} \;
Run Code Online (Sandbox Code Playgroud)
主要是因为我不明白这-exec部分是如何工作的。大括号、反斜杠和分号的含义是什么?该语法还有其他用例吗?
Kus*_*nda 176
这个答案来自以下部分:
-exec-exec结合sh -c-exec ... {} +-execdir-exec该-exec选项采用带有可选参数的外部实用程序作为其参数并执行它。
如果字符串{}出现在给定命令的任何位置,它的每个实例都将被当前正在处理的路径名替换(例如./some/path/FILENAME)。在大多数 shell 中,这两个字符{}不需要被引用。
该命令需要以;forfind结束才能知道它在哪里结束(因为之后可能有更多选项)。为了保护;免受shell 的影响,它需要被引用为\;or ';',否则 shell 会将其视为find命令的结尾。
示例(\前两行的末尾仅用于续行):
find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'
Run Code Online (Sandbox Code Playgroud)
这将找到-type f名称与*.txt当前目录中或当前目录下的模式匹配的所有常规文件 ( ) 。然后它将测试该字符串是否hello出现在任何找到的文件中grep -q(它不产生任何输出,只是一个退出状态)。对于那些包含字符串的文件,cat将执行以将文件内容输出到终端。
每个-exec也都像对由 找到的路径名进行“测试” find,就像-type和-name一样。如果命令返回零退出状态(表示“成功”),find则考虑命令的下一部分,否则find命令继续使用下一个路径名。这在上面的示例中用于查找包含 string 的文件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"
Run Code Online (Sandbox Code Playgroud)
这将两个参数传递给子 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" {} ';'
Run Code Online (Sandbox Code Playgroud)
在内部脚本中,$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" {} ';'
Run Code Online (Sandbox Code Playgroud)
或者,在内部脚本中添加变量:
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" {} ';'
Run Code Online (Sandbox Code Playgroud)
请注意,在这最后的变动,变量from以及to在子shell是从外部脚本中的相同名称的变量不同。
以上是从-execwith调用任意复杂脚本的正确方法find。find在循环中使用
for pathname in $( find ... ); do
Run Code Online (Sandbox Code Playgroud)
容易出错且不优雅(个人意见)。它在空格上拆分文件名,调用文件名通配符,并且还强制 shellfind在运行循环的第一次迭代之前扩展完整结果。
也可以看看:
-exec ... {} +所述;在端部可以通过替换+。这会导致find使用尽可能多的参数(找到的路径名)执行给定的命令,而不是为每个找到的路径名执行一次。  该字符串{}  必须出现在 之前+,它才能工作。
find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +
Run Code Online (Sandbox Code Playgroud)
在这里,find将收集生成的路径名并一次执行cat尽可能多的路径名。
find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +
Run Code Online (Sandbox Code Playgroud)
同样在这里,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" {} +
Run Code Online (Sandbox Code Playgroud)
-execdir还有-execdir(由大多数find变体实现,但不是标准选项)。
这-exec与不同之处在于,给定的 shell 命令使用找到的路径名的目录作为其当前工作目录执行,并且{}将包含找到的路径名的基本名称而不包含其路径(但 GNUfind仍将基本名称前缀为./,而 BSDfind或sfind不会)。
例子:
find . -type f -name '*.txt' \
    -execdir mv -- {} 'done-texts/{}.done' \;
Run Code Online (Sandbox Code Playgroud)
这会将每个找到的*.txt-file移动到与找到文件的位置相同的目录中的预先存在的done-texts子目录。该文件也将通过向其添加后缀来重命名。, 在这些实现中需要标记选项的结尾,而这些实现不以. 围绕包含参数两边的引号不是作为一个整体需要,如果你的shell是。另请注意,并非所有实现都会在那里扩展(不会)。.done--find./{}(t)cshfind{}sfind
这会有点棘手,-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 {} +
Run Code Online (Sandbox Code Playgroud)
或者,
find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +
Run Code Online (Sandbox Code Playgroud)