如何在sh中使用'find'命令的'-prune'选项?

277

我不太理解man find给出的示例,有人能给我一些示例和解释吗?我可以在其中结合使用正则表达式吗?


更详细的问题如下:

编写一个名为changeall的shell脚本,其接口类似于changeall [-r|-R] "string1" "string2"。它将查找所有后缀为.h.C.cc.cpp的文件,并将所有string1的出现替换为string2。选项-r表示仅在当前目录下或包括子目录中查找。

注意:

  1. 对于非递归情况,不允许使用ls,只能使用findsed
  2. 我尝试过find -depth,但它不被支持。这就是为什么我想知道-prune是否有帮助,但我不理解来自man find的示例。

编辑2:我在做作业,我没有详细说明问题,因为我想自己完成它。由于我已经完成并提交了作业,现在我可以陈述整个问题。此外,我设法在不使用-prune的情况下完成了任务,但仍想了解它。

10个回答

557
我发现关于-prune的令人困惑之处在于它是一个动作(如-print),而不是一个测试(如-name)。它改变了“待办事项”列表,但始终返回true。
使用-prune的一般模式如下:
find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

通常情况下,您需要立即在-prune之后使用-o(逻辑或),因为测试的第一部分(直到-prune为止)将对实际想要的内容(即您不想要修剪的内容)返回 false

这是一个示例:

find . -name .snapshot -prune -o -name '*.foo' -print

这将查找不在“.snapshot”目录下的“*.foo”文件。在这个例子中,-name .snapshot 组成了 [要删除的条件],而 -name '*.foo' -print[你通常的条件][要执行的操作]重要提示:
  1. If all you want to do is print the results you might be used to leaving out the -print action. You generally don't want to do that when using -prune.

    The default behavior of find is to "and" the entire expression with the -print action if there are no actions other than -prune (ironically) at the end. That means that writing this:

     find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS
    

    is equivalent to writing this:

     find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS
    

    which means that it'll also print out the name of the directory you're pruning, which usually isn't what you want. Instead it's better to explicitly specify the -print action if that's what you want:

     find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
    
  2. If your "usual condition" happens to match files that also match your prune condition, those files will not be included in the output. The way to fix this is to add a -type d predicate to your prune condition.

    For example, suppose we wanted to prune out any directory that started with .git (this is admittedly somewhat contrived -- normally you only need to remove the thing named exactly .git), but other than that wanted to see all files, including files like .gitignore. You might try this:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS
    

    This would not include .gitignore in the output. Here's the fixed version:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS
    

额外提示:如果您正在使用GNU版本的find find的texinfo页面比其manpage(大多数GNU工具都是如此)有更详细的解释。


19
加1赞给你优秀的解释(尤其是重要的提示)。你应该将此提交给开发人员(因为手册未为普通人解释“prune”^^ 对我来说尝试了很多次才弄清楚,而且我没有看到你警告我们的副作用)。 - Olivier Dulac
1
你还可以将其与“-exec”子句一起使用,以标记一个目录,并指示不应下降到该目录。为此,您需要使用多行“-exec”版本,如https://unix.stackexchange.com/a/507025/369126所示,可能如下所示:find $dir -type d -exec sh -c 'test -f $1/DONTBACKUP' sh {} \; -prune -o morestuff我们从$dir开始,找到的任何目录都会被测试是否包含名为DONTBACKUP的文件。如果存在(即“-exec”的退出状态为0),则跳过该目录,否则继续进行morestuff - plijnzaad
1
以更多的计算周期为代价,我经常能够避免使用“-prune”,而是使用“! -path”。例如,为了避免进入名为“archive”的文件夹,我使用“find folder1 folder2 ! -path '/archive/'”。 - user2153235
在我的情况下,通过以下命令返回排除目录(但不包括内容):find . -name "*.c" -o -path "./build" -prune。因此,它会返回一堆不在./build/./build/中的 *.c 文件。 - Puck
2
@Puck,你需要添加一个明确的-print操作来获得正确的行为。你的子句与答案中推荐的方式相反。通常我会写出你想要做的事情:find . -path "./build" -prune -o -name "*.c" -print。然而,如果你更喜欢修剪后的内容在最后,那也可以,但你需要在-o之前插入打印操作:find . -name "*.c" -print -o -path "./build" -prune。有关更多详细信息,请参见“重要说明”#1。 - Laurence Gonsalves
1
@user2153235 这是一个很好的提示!\! -path 更容易理解,并且适用于按路径名修剪的常见情况。但是,-prune 更加通用,因为它可以基于任何谓词进行修剪,而不仅仅是路径。 - Laurence Gonsalves

37

通常情况下,在Linux中我们做事情的方式是从左到右。

你首先会写下你要查找的内容:

find / -name "*.php"

然后,你按下回车键,意识到从你不想搜索的目录中获取了太多文件。

因此,你想:“让我们排除 /media ,以避免搜索已挂载的驱动器。”

现在,你只需要将以下内容附加到之前的命令中:

-print -o -path '/media' -prune

最后的命令是:

find / -name "*.php" -print -o -path '/media' -prune
|<--      Include      -->|<--      Exclude      -->|

我认为这个结构更简单,并且符合正确的方法。


31

请注意,-prune并不像有些人说的那样防止进入任何目录。它只能阻止进入与应用它的测试相匹配的目录。也许一些示例会有所帮助(有一个正则表达式示例,请参见底部)。抱歉这篇文章如此冗长。

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

21
在其他答案给出的建议基础上(我没有声望来创建回复)......当将-prune与其他表达式组合时,行为会因使用的其他表达式而有微妙的差异。@Laurence Gonsalves的示例将找到不在“.snapshot”目录下的“*.foo”文件:-
find . -name .snapshot -prune -o -name '*.foo' -print

然而,这种稍微不同的缩写方法可能会无意中列出 .snapshot 目录 (以及任何嵌套的 .snapshot 目录):-

find . -name .snapshot -prune -o -name '*.foo'

根据posix手册,原因是:

如果给定的表达式不包含以下任何一个条件-primary -exec、-ls、-ok或-print,则给定的表达式将被有效地替换为:

(给定的表达式) -print

也就是说,第二个示例等同于输入以下内容,从而修改术语的分组:

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

这个问题至少在 Solaris 5.10 上出现过。我用 *nix 的不同版本已经有大约 10 年的时间了,但是直到最近才开始寻找造成这种情况的原因。


4

我不是这方面的专家(这个页面和http://mywiki.wooledge.org/UsingFind对我很有帮助)

刚刚注意到-path是用于匹配在find之后紧随其后的字符串/路径(在这些示例中为.),而-name匹配所有基本名称。

find . -path ./.git  -prune -o -name file  -print

在当前目录下,通过(.)查找后,会屏蔽掉.git目录。

find . -name .git  -prune -o -name file  -print

该命令会递归地阻止所有.git子目录。

请注意,./非常重要!!-path必须匹配以.或find后面的任何内容为锚点的路径。如果您没有使用它(从另一侧的'-o'),则可能无法修剪!我天真地不知道这一点,这使我不想使用-path,但当您不想修剪所有具有相同基本名称的子目录时,它非常好用:D


3

find命令会创建一个文件列表。它对每个文件应用您提供的谓词,并返回通过筛选的文件。

对我来说,-prune表示从结果中排除这个想法真的很令人困惑。您可以在没有prune的情况下排除文件:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

-prune 只是改变了搜索的行为。如果当前匹配的是一个目录,它会告诉 "嘿,find,那个文件你刚才匹配上了,不要进入它"。它只会从要搜索的文件列表中移除这棵树(但不会删除该文件本身)。

它应该被命名为 -dont-descend


2
显示目录本身以及它的内容,但不包括其冗长乏味的内容:
find . -print -name dir -prune

2

Prune是一个“不要递归此文件”的开关(动作)。

根据man页面:

如果未给出-depth,则为真; 如果文件是目录,则不要进入其中。 如果指定了-depth,则为假;没有影响。

基本上,它不会进入任何子目录。

以这个例子为例:

您有以下目录:

% find home
home
home/test1
home/test1/test1
home/test2
home/test2/test2

find home -name test2 会打印出以test2 命名的父目录和子目录:

% find home -name test2
home/test2
home/test2/test2

现在,使用-prune选项......

find home -name test2 -prune命令只会输出/home/test2;它不会进入/home/test2目录查找/home/test2/test2
% find home -name test2 -prune
home/test2

2
不是百分之百准确的:当匹配条件时进行修剪,如果它是一个目录,则将其从待办列表中删除,即不要进入其中。-prune也适用于文件。 - Olivier Dulac

1
如果您阅读了这里所有好的答案,那么我现在的理解是以下所有内容都会返回相同的结果:
find . -path ./dir1\*  -prune -o -print

find . -path ./dir1  -prune -o -print

find . -path ./dir1\*  -o -print
#look no prune at all!

但是最后一个需要更长时间,因为它仍然在dir1中搜索所有内容。 我想真正的问题是如何使用-or筛选掉不想要的结果而不必实际搜索它们。

所以我想prune意味着不要继续匹配,但将其标记为完成...

http://www.gnu.org/software/findutils/manual/html_mono/find.html “这并不是由于‘-prune’操作的影响(它只防止进一步下降,而不是确保我们忽略该项)。 相反,这种效果是由‘-o’的使用造成的。 由于左侧的“或”条件已成功匹配./src/emacs,因此对于此特定文件,根本不需要评估右侧的(‘-print’)。”


0

有相当多的答案;其中一些理论过重。我将留下为什么我需要修剪,以便可能对需要先/例如解释有用 :)

问题

我有一个文件夹,其中包含约20个节点目录,每个目录都有其预期的node_modules目录。

一旦您进入任何项目,您会看到每个../node_modules/module。但是您知道如何处理。几乎每个模块都有依赖项,因此您看到的更像是projectN/node_modules/moduleX/node_modules/moduleZ...

我不想被依赖项淹没...

知道-d n / -depth n,它对我没有帮助,因为我想要的每个项目的主/第一个node_modules目录位于不同的深度,就像这样:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

如何获取以第一个node_modules结尾的路径列表并移动到下一个项目以获取相同的内容?

输入-prune

当您添加-prune时,仍将进行标准递归搜索。分析每个“路径”,并且每次找到都会被输出,find会继续像好人一样往下挖掘。但是我不想要更多的node_modules

因此,在任何这些不同的路径中,-prune将在找到您的项目后停止沿着特定路径进一步挖掘。在我的情况下,是node_modules文件夹。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接