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

der*_*dji 207 regex shell find manual

我不太明白"男人发现"给出的例子,有人能给我一些例子和解释吗?我可以在其中组合正则表达式吗?


更详细的问题是这样的:写一个shell脚本,changeall,它有一个像"changeall [-r | -R]"string1""string2"这样的接口.它将找到后缀为.h,.C的所有文件,.cc或.cpp并将所有出现的"string1"更改为"string2".- r是仅保留当前目录或包含subdir的选项.注意:1)对于非递归情况,不允许"ls" ,我们只能使用'find'和'sed'.2)我试过'find -depth'但它不受支持.这就是为什么我想知道'-prune'是否可以帮助,但是不明白'男人发现'.


EDIT2:我正在做作业,我没有详细提问,因为我想自己完成.既然我已经完成并把它交给我,现在我可以陈述整个问题.此外,我设法在不使用-prune的情况下完成了作业,但无论如何都想学习它.

Lau*_*ves 418

我发现令人困惑的事情-prune是它是一个动作(比如-print),而不是一个测试(比如-name).它改变了"待办事项"列表,但总是返回true.

使用的一般模式-prune是:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]
Run Code Online (Sandbox Code Playgroud)

你几乎总是想要-o紧接着-prune,因为测试的第一部分(包括-prune)将为你真正想要的东西返回false(即:你不想修剪掉的东西).

这是一个例子:

find . -name .snapshot -prune -o -name '*.foo' -print
Run Code Online (Sandbox Code Playgroud)

这将找到不在".snapshot"目录下的"*.foo"文件.在这个例子中,-name .snapshot是"你想要修剪的东西的测试",并且[conditions to prune]是"你通常在路径之后放置的东西".

重要说明:

  1. 如果你想要做的就是打印结果,你可能会习惯于忽略这个-name '*.foo' -print动作.您通常希望在使用时这样做[your usual conditions].

    如果除了(具有讽刺意味)最后没有其他操作,则find的默认行为是"和" 整个表达式.这意味着写下这个:[actions to perform]-print

    find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS
    
    Run Code Online (Sandbox Code Playgroud)

    相当于写这个:

    find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS
    
    Run Code Online (Sandbox Code Playgroud)

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

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果您的"通常条件"碰巧匹配与您的剪枝条件匹配的文件,那么这些文件将不会包含在输出中.解决此问题的方法是-print在剪枝条件中添加谓词.

    例如,假设我们想要删除任何以某个开头的目录-prune(这无疑是有点人为的 - 通常你只需要删除完全 命名的东西-print),但除此之外想要查看所有文件,包括像-type d.你可以试试这个:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS
    
    Run Code Online (Sandbox Code Playgroud)

    包括.git在输出中.这是固定版本:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS
    
    Run Code Online (Sandbox Code Playgroud)

额外提示:如果您使用的是GNU版本.git,则texinfo页面的.gitignore解释比其联机帮助页更详细(大多数GNU实用程序都是如此).

  • 和+1为你做了很好的解释(尤其是重要的注释).你应该把这个提交给find开发者(因为手册页没有解释正常人的"修剪"^^我花了很多时间试图解决它,我没有看到你警告我们的副作用) (11认同)
  • 它在你的文本中并不是100%显而易见(但是因为你只打印'*.foo'它并不冲突)但-prune部分也不会打印任何名为".snapshot"的东西(不仅是目录).即,`-prune`不仅适用于目录(但是,对于目录,它也会阻止进入与该条件匹配的目录,即这里的dirs匹配`-name .snapshot`). (6认同)
  • 请注意," - o"是"-or"的简写,其中(虽然不符合POSIX)读取更清楚. (6认同)
  • +1终于找到了为什么我最后需要`-print`,我现在可以停止添加`\!-path <pattern>`除了`-prune` (3认同)
  • @OlivierDulac关于潜在地剥离要保留的文件,这是一个很好的观点。我已经更新了答案以澄清这一点。顺便说一句,实际上不是`-prune`本身会导致这种情况。问题是or运算符“短路”,并且or的优先级低于和。最终结果是,如果遇到名为.snapshot的文件,则它将与第一个-name匹配,然后-prune将不执行任何操作(但返回true),然后or会因为其左参数而返回true是真的 该动作(例如:-print)是其第二个参数的一部分,因此它永远没有执行的机会。 (2认同)
  • @Puck您需要添加显式的“-print”操作才能获得正确的行为。您的条款与答案中建议的方式相反。我通常会把你想要做的事情写成:`find . -path“./build”-prune -o -name“*.c”-print`。但是,如果您更喜欢最后修剪的内容,也可以工作,但您需要在 `-o`: `find 之前插入 print 操作。-名称“*.c”-print -o -路径“./build”-prune`。有关更多详细信息,请参阅“重要说明”#1。 (2认同)

Pau*_*ce. 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
Run Code Online (Sandbox Code Playgroud)


Ami*_*itP 25

通常我们在linux中做本地的方式和我们的思维方式是从左到右.
所以你会先写下你想要的东西:

find / -name "*.php"
Run Code Online (Sandbox Code Playgroud)

然后你可能点击进入并意识到你从你不希望的目录中获取太多文件.我们排除/ media以避免搜索已安装的驱动器.
您现在应该将以下命令附加到上一个命令:

-print -o -path '/media' -prune
Run Code Online (Sandbox Code Playgroud)

所以最后的命令是:

find / -name "*.php" -print -o -path '/media' -prune
Run Code Online (Sandbox Code Playgroud)

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

我认为这种结构更容易,并与正确的方法相关联

  • 我不会期望这是有效的 - 我会认为它会在修剪之前首先评估左子句,但令我惊讶的是,快速测试似乎表明`find`足够聪明来处理`-prune`第一句.嗯,有趣. (3认同)
  • @artfulrobot真的是先处理吗?我以为它正在进入`/ media`,注意到它没有被称为`* .php`,然后检查它当前是否在`/ media`里面,看到它在里面,因此跳过了整个子树。它仍然是从左到右的,只要两个检查不重叠就没有区别。 (2认同)

crw*_*crw 10

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

-prune与其他表达式组合时,根据使用的其他表达式,行为存在细微差别.

@Laurence Gonsalves的例子会找到不在".snapshot"目录下的"*.foo"文件: -

find . -name .snapshot -prune -o -name '*.foo' -print
Run Code Online (Sandbox Code Playgroud)

但是,这个略有不同的简写,也许是在不经意间,也会列出.snapshot目录(以及任何嵌套的.snapshot目录): -

find . -name .snapshot -prune -o -name '*.foo'
Run Code Online (Sandbox Code Playgroud)

原因是(根据我系统的联机帮助页): -

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

(given_expression)-print

也就是说,第二个例子相当于输入以下内容,从而修改术语的分组: -

find . \( -name .snapshot -prune -o -name '*.foo' \) -print
Run Code Online (Sandbox Code Playgroud)

这至少在Solaris 5.10上已经出现过.使用各种口味的*nix大约10年后,我最近才搜索出这种情况发生的原因.


sab*_*ton 5

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

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

find . -path ./.git  -prune -o -name file  -print
Run Code Online (Sandbox Code Playgroud)

阻止当前目录中的 .git 目录如您在 中的发现.

find . -name .git  -prune -o -name file  -print
Run Code Online (Sandbox Code Playgroud)

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

注意这./ 一点非常重要!! -path必须匹配锚定到的路径. 或 find 之后出现的任何内容,如果您得到没有它的匹配项(来自 or ' -o' 的另一侧),则可能没有被修剪!我天真地没有意识到这一点,当你不想修剪具有相同基本名称的所有子目录时,它让我使用 -path ,这很棒:D