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


219

我不太了解中提供的示例man find,有人可以给我一些示例和解释吗?我可以在其中结合正则表达式吗?


更详细的问题是这样的:

编写一个shell脚本,changeall该脚本的接口类似于changeall [-r|-R] "string1" "string2"。它会找到的所有文件的后缀.h.C.cc,或.cpp和更改所有出现string1string2-r是仅保留当前目录或包含子目录的选项。

注意:

  1. 对于非递归情况,ls不允许使用,我们只能使用findsed
  2. 我尝试过,find -depth但不支持。这就是为什么我想知道是否-prune可以提供帮助,但不了解的示例的原因man find

EDIT2:我正在做作业,我没有详细询问问题,因为我想自己完成。既然我已经完成并提交了,现在我可以陈述整个问题。另外,我设法不使用来完成任务-prune,但无论如何都想学习。

Answers:


438

我发现令人困惑的-prune是它是一个动作(如-print),而不是测试(如-name)。它更改了“待办事项”列表,但始终返回true

使用的一般模式-prune是这样的:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

你几乎总是要的-o(逻辑OR)后-prune,因为测试的是第一部分(直至并包括-prune)将返回的你真正想要的东东(即:你的东西不要想修剪出)。

这是一个例子:

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

这将找到不在“ .snapshot”目录下的“ * .foo”文件。在此示例中,-name .snapshot组成[conditions to prune]和,-name '*.foo' -print[your usual conditions][actions to perform]

重要说明

  1. 如果您只想打印结果,则可能会习惯于省略-print操作。使用时,您通常希望这样做-prune

    find的默认行为是,如果最后没有(讽刺地)没有其他动作,则用动作“和” 整个表达式。这意味着编写此代码:-print-prune

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

    等效于编写此代码:

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

    这意味着它还会打印出您正在修剪的目录的名称,通常不是您想要的。相反,-print如果这是您想要的,则最好明确指定操作:

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
  2. 如果您的“正常情况”恰好与也符合修剪条件的文件相匹配,则这些文件将包含在输出中。解决此问题的方法是-type d在修剪条件中添加谓词。

    举例来说,假设我们想.git删掉所有以(确实有些人为设计-通常只需要删除名称完全正确 的东西.git)开头的目录,但除了该目录以外,还想查看所有文件,包括诸如的文件.gitignore。您可以尝试以下方法:

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

    这将包括.gitignore在输出中。这是固定版本:

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

额外提示:如果您使用的GNU版本find,则texinfo页面的find说明比其手册页更详细(大多数GNU实用程序都是如此)。


6
它在您的文本中并不是100%明显(但是因为您只打印'* .foo',所以不会冲突),但是-prune部分也不会打印任何名称为“ .snapshot”的内容(不仅是目录)。即,-prune不仅在目录上起作用(而且,对于目录,它也确实阻止输入与该条件匹配的目录,即此处与该目录匹配的目录-name .snapshot)。
Olivier Dulac

12
并为您+1作了很好的说明(尤其是重要说明)。您应该将此内容提交给查找开发人员(因为手册页中没有为普通人解释“修剪” ^^,我花了很多努力才弄清楚,并且我没有看到您警告我们的副作用)
Olivier Dulac

2
@OlivierDulac关于潜在地剥离要保留的文件,这是一个很好的观点。我已经更新了答案以澄清这一点。-prune顺便说一句,实际上并不是导致这种情况的本身。问题是or运算符“短路”,并且or的优先级低于和。最终结果是,如果.snapshot遇到一个名为的文件,它将与first匹配-name-prune然后不执行任何操作(但返回true),然后or或return true,因为其左参数为true。该动作(例如:)-print是其第二个参数的一部分,因此它永远没有执行的机会。
劳伦斯·贡萨尔维斯

3
+1终于找到了为什么我需要-print在最后,我现在可以停止增加\! -path <pattern>-prune
惨变

6
请注意,“-o”是“ -or”的简写,它(虽然不符合POSIX)读起来更清晰。
2014年

27

通常,我们在linux中执行事务的本机方式是从左到右。
因此,您将首先写下您要寻找的内容:

find / -name "*.php"

然后,您可能会按Enter键,并意识到您从目录中获取了过多的文件,而您不希望这样做。让我们排除/ media以避免搜索已安装的驱动器。
现在,您应该将以下内容附加到上一个命令:

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

所以最后的命令是:

find / -name "*.php" -print -o -path '/media' -prune

............... | <---包括---> | ........... | <- --------排除---------> |

我认为这种结构要容易得多,并且与正确的方法相关


3
我不希望这会很有效-我以为它将在修剪之前首先评估left子句,但令我惊讶的是,快速测试似乎表明find足够聪明才能首先处理该-prune子句。嗯,有趣。
artfulrobot 2014年

我从来没有考虑过在使用GNU的近十年来找到它!谢谢你!-prune从现在开始,它肯定会改变我的思考方式。
费利佩·阿尔瓦雷斯

3
@artfulrobot真的是先处理吗?我以为它正在进入/media,注意到它没有被调用*.php,然后检查它当前是否在里面/media,看到它在里面,因此跳过了整个子树。它仍然是从左到右的,只要两个检查不重叠就没有区别。
phk

26

注意-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

如果您还“触摸./dir1/scripts/test”(即在打印出的子目录中有一个“ test”文件,而不是dir),它将不会被find . -name test -prune -o -print:iow打印,-prune这也是一个动作在文件上工作
Olivier Dulac

10

添加到其他答案中给出的建议(我没有代表来创建回复)...

-prune与其他表达式结合使用时,行为上会有细微的差别,具体取决于使用了哪些其他表达式。

@Laurence Gonsalves的示例将找到不在“ .snapshot”目录下的“ * .foo”文件:

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

但是,这种略有不同的捷径可能也会无意中列出.snapshot目录(以及任何嵌套的.snapshot目录):

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

原因是(根据我系统上的手册):

如果给定表达式不包含任何主-exec,-ls,-ok或-print,则有效替换为:

(given_expression)-打印

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

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

至少在Solaris 5.10上可以看到这一点。在使用各种形式的* nix大约10年之后,我才刚刚搜索到这种情况发生的原因。


感谢您注意使用-prunewith和不使用之间的区别-print
mcw

3

修剪是不递归的任何目录切换。

从手册页

如果未提供-depth,则为true;否则为true。如果文件是目录,请不要进入该目录。如果给定-depth,则为false;否则为false。没有效果。

基本上,它不会降级到任何子目录中。

举个例子:

您有以下目录

  • / home / test2
  • / home / test2 / test2

如果您运行find -name test2

它将返回两个目录

如果您运行find -name test2 -prune

它只会返回/ home / test2,因为它不会下降到/ home / test2来查找/ home / test2 / test2


不是100%正确:它是“在匹配条件时进行修剪,并且如果它是目录,请将其从待办事项列表中删除,即也不要输入”。-prune也可用于文件。
Olivier Dulac

2

我不是这个专家(该页面与http://mywiki.wooledge.org/UsingFind一起非常有用)

刚刚注意到的-path是,一条路径完全匹配find.紧随其后的字符串/路径(在这些示例中),其中as -name匹配所有基本名称。

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

阻止当前目录中的.git目录在中找到.

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

递归阻止所有.git子目录。

注意,这./ 是非常重要的! -path必须匹配定位到的路径,. 或者找到之后的任何内容(如果从或' -o' 的另一端)找到匹配的内容,则可能没有被修剪!我天真地没有意识到这一点,当您不想修剪具有相同基本名称的所有子目录时,它使我使用-path很好:


请注意,如果您说的话,find bla/那么您将需要-path bla/.git(或者如果您*在前面推a ,则其行为更像-name)
sabgenton 2013年

1

显示所有内容,包括dir本身,但不显示其冗长的内容:

find . -print -name dir -prune

0

如果您在这里阅读了所有好的答案,那么我现在的理解是,以下所有结果均返回相同的结果:

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

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

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

但是最后一个将花费更长的时间,因为它仍会搜索dir1中的所有内容。我想真正的问题是如何在-or不实际搜索结果的情况下找出不需要的结果。

所以我想修剪意味着过去的比赛不像样,但是将其标记为完成...

http://www.gnu.org/software/findutils/manual/html_mono/find.html “但是,这不是由于'-prune'动作(仅防止进一步下降,它不能确保我们忽略了该项目。)相反,这种效果是由于使用了-o。由于“ /”条件的左侧已成功用于./src/emacs,因此无需评估右侧-此特定文件的全部(“打印”)。”


0

find建立文件列表。它将您提供的谓词应用于每个谓词,并返回通过的谓词。

这个想法-prune意味着将结果排除在外,这确实让我感到困惑。您可以排除没有修剪的文件:

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

所有的-prune事情都是改变搜索的行为。如果当前匹配项是目录,则显示“嘿find,您刚刚匹配的文件,请不要进入该目录”。它只是从要搜索的文件列表中删除该树(而不是文件本身)。

它应命名为-dont-descend


0

有很多答案。其中有些过于理论化。我将离开为什么我需要修剪一次的原因,所以也许需求优先/示例说明对某人有用:)

问题

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

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

我不想被列表的依赖淹没...

知道-d n/ -depth n,对我没有帮助,因为我希望每个项目的main / first 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它将find停止进一步挖掘该特定途径。就我而言,该node_modules文件夹。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.