我发现自己一直在查询

find . -name "FILENAME"  -exec rm {} \;
的语法

主要是因为我看不到-exec部分的工作原理。花括号,反斜杠和分号的含义是什么?该语法还有其他用例吗?

评论

@Philippos:我明白你的意思。请记住,手册页是参考,即对于那些了解此事的人有用,以查找语法。对于刚接触该主题的人来说,他们通常是神秘且正式的才有用。您会发现接受的答案是手册页条目长度的大约10倍,这是有原因的。

甚至旧的POSIX手册页都读取了一个Utility_name或仅包含两个字符“ {}”的自变量应替换为当前路径名,这对我来说似乎足够了。另外,它有一个-exec rm {} \;的示例,就像您的问题一样。在我的时代,除了“大灰墙”之外,几乎没有其他资源,这些手册是印刷的手册页(纸张比存储便宜)。因此,我知道这对于刚接触该主题的人就足够了。您的最后一个问题是公平的。不幸的是,@ Kusalananda和我自己都没有答案。

@Philippos :-)哦,尝试一下,这是令人兴奋的事情,因为“令人兴奋”的程度超出了我的图表范围。

Comeon @Philippos。您是真的告诉Kusalananda他的手册页没有改善吗? :-)

@ZsoltSzilagy我既没有告诉也没有那个意思。他确实很好地喂养了您,我只是认为您已经够大了,可以自己吃饭了。 (-;

#1 楼

答案来自以下部分:


-exec的基本用法

结合使用-execsh -c

结合使用-exec ... {} +
/>
使用-execdir



-exec的基本用法


-exec选项采用带有可选参数的外部实用程序作为其参数并执行它。

如果字符串{}存在于给定命令的任何位置,则它的每个实例都将被当前正在处理的路径名替换(例如./some/path/FILENAME)。在大多数Shell中,不需要将两个字符{}引起来。

该命令需要以;终止,以便find知道它的结束位置(因为之后可能会有其他选择)。为了保护;不受外壳影响,需要将其引用为\;';',否则外壳会将其视为find命令的结尾。

示例(第一个结尾处的\两行只是连续的行):然后,它将使用-type f(不会产生任何输出,只是退出状态)测试字符串*.txt是否出现在找到的任何文件中。对于包含字符串的那些文件,将执行hello将文件的内容输出到终端。每个grep -q都对cat找到的路径名像“ test”一样,就像-execfind可以。如果该命令返回零退出状态(表示“成功”),则考虑-type命令的下一部分,否则-name命令以下一个路径名继续。上面的示例中使用它来查找包含字符串find的文件,但忽略所有其他文件。

上面的示例说明了find的两个最常见的用例:


作为进一步限制搜索的测试。
对找到的路径名执行某种操作(通常(但不一定是在hello命令的末尾)。


结合使用-execfind


-exec可以执行的命令仅限于带有可选参数的外部实用程序。不能直接与sh -c一起使用shell内置函数,条件,条件,管道,重定向等,除非包裹在-exec子外壳之类的东西中。

如果需要-exec功能,请使用sh -c代替bash

bash -c运行sh -c并在命令行中提供了脚本,随后是该脚本的可选命令行参数。

使用sh -c的简单示例由本身,没有/bin/sh

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'


这会将两个参数传递给子shell脚本。这些将放置在sh -cfind中供脚本使用。


字符串。这将在脚本内以sh的形式提供,并且如果内部外壳输出错误消息,则会在该字符串的前面加上前缀。
参数apples在脚本中以的形式提供,并且有更多的参数,那么它们本来可以作为等获得。它们也将在列表"$@"中可用(但"$@"除外,后者不属于-exec)。它使我们可以制作任意复杂的脚本,以对find所找到的路径名起作用。后缀保留在变量中:

sh -c 'echo  "You gave me , thanks!"' sh "apples"


在内部脚本中,是字符串text是字符串txt是为我们找到的任何路径名find。参数扩展${3%.}将采用路径名并从中删除后缀.text。或者,使用dirname / basename

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%.}."' sh "$from" "$to" {} ';'


,或者,在内部脚本中添加了变量的地方:在外部脚本中。

上面是从fromto调用任意复杂脚本的正确方法。在类似

find . -type f -name "*.$from" -exec sh -c '
    mv "" "$(dirname "")/$(basename "" ".")."' sh "$from" "$to" {} ';'


的循环中使用-exec容易出错且不雅(个人见解)。它正在空格上分割文件名,调用文件名遍历,并且甚至在运行循环的第一次迭代之前,还强制Shell扩展find的完整结果。 />
为什么循环遍历find的输出错误做法?
可以安全地使用`find -exec sh -c`吗?



使用find
/>

最后的find可能会替换为-exec ... {} +。这使;用尽可能多的参数(找到的路径名)执行给定命令,而不是为每个找到的路径名执行一次。字符串+必须恰好在find之前出现,这样才能起作用。

find . -type f -name "*.$from" -exec sh -c '
    from=; to=; pathname=
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'


这里,{}将收集生成的路径名并一次对尽可能多的路径名执行+

for pathname in $( find ... ); do


与这里一样,find将执行最少的次数。最后一个示例需要coreutils的GNU cat(支持mv选项)。

使用mv也是一种通过任意复杂的脚本循环遍历一组路径名的有效方法。

基本用法与使用-t时相同,但是脚本现在需要更长的参数列表。这些可以通过在脚本内部遍历-exec sh -c ... {} +来遍历。

上一节中的示例更改了文件名后缀:

br />使用-exec sh -c ... {} ';'


还有"$@"(由大多数-execdir变体实现,但不是标准选项)。给定的shell命令以找到的路径名的目录作为它的当前工作目录执行,并且-execdir将包含找到的路径名的基名而没有其路径(但是GNU find仍将基名加上-exec前缀,而BSD {}不会做到这一点)。

示例:

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +


这会将每个找到的find-文件移动到同一目录中预先存在的./子目录中文件所在的位置。该文件还将通过添加后缀find重命名。文件的新名称。我们还需要*.txt的目录名才能正确定位done-texts目录。

使用.done可以使诸如此类的事情变得更容易。必须使用子外壳:

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +


或者,

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=; to=
    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" {} +


评论


-exec接受程序和参数并运行它;一些shell命令仅由程序和参数组成,而许多则不包括。 Shell命令可以包括重定向和管道; -exec不能(尽管整个发现都可以重定向)。一个shell命令可以使用; &&如果等; -exec无法执行,尽管-a -o可以执行一些操作。 Shell命令可以是别名或Shell函数,也可以是内置命令。 -exec不能。 shell命令可以扩展vars。 -exec不能(尽管运行find的外壳可以)。 Shell命令每次可以用不同的$(command)替代; -exec不能。 ...

–dave_thompson_085
17年9月1日在16:02



说这是一个shell命令是错误的,找到-exec cmd arg \;不会调用Shell来解释Shell命令行,而是直接运行execlp(“ cmd”,“ arg”),而不是execlp(“ sh”,“-c”,“ cmd arg”)如果没有内置cmd,则最终执行等效于execlp(“ cmd”,“ arg”)的操作。

–StéphaneChazelas
17年9月26日在11:03

您可以阐明,所有查找参数在-exec之后至;或+组成要与命令一起执行的命令,将{}参数的每个实例替换为当前文件(带有;),并将{}作为最后一个参数,而+之前的+替换为文件列表作为单独的参数( (在{} +情况下)。 IOW -exec带有多个参数,以;终止;或{} +。

–StéphaneChazelas
17-09-26在11:25



@Kusalananda您的最后一个示例也不能使用以下更简单的命令:find。 -type f -name'* .txt'-exec sh -c“ mv $ 1 $(dirname $ 1)/ done-texts / $(basename $ 1).done” sh {}';' ?

– Atralb
20年7月8日在22:58

@Atralb是的,它也可以工作,并且与最后一段代码具有相同的效果,但是不是在循环中运行mv,而是对每个找到的文件执行一次,而是对每个找到的文件执行sh和mv,这很明显处理大量文件的速度较慢。

– Kusalananda♦
20年7月9日在6:52