关于Linux:如何排除find命令中的目录

How to exclude a directory in find . command

我试图对所有的javascript文件运行一个find命令,但是如何排除一个特定的目录?

这是我们使用的find代码。

1
2
3
4
for file in $(find . -name '*.js')
do
  java -jar config/yuicompressor-2.4.2.jar --type js $file -o $file
done


如果-prune不适用于您,这将:

1
find -name"*.js" -not -path"./directory/*"


使用prune开关,例如,如果要排除misc目录,只需将-path ./misc -prune -o添加到find命令:

1
find . -path ./misc -prune -o -name '*.txt' -print

下面是一个具有多个目录的示例:

1
find . -type d \( -path dir1 -o -path dir2 -o -path dir3 \) -prune -o -print

在这里,我们排除了dir1、dir2和dir3,因为在find表达式中,它是一种作用于标准-path dir1 -o -path dir2 -o -path dir3(如果dir1、dir2或dir3),并且与type -d一起作用的作用。下一步行动是-o print,只需打印。


我发现以下比其他建议的解决方案更容易解释:

1
find build -not \( -path build/external -prune \) -name \*.js

这来自于一个实际的用例,在这个用例中,我需要对WinterSmith生成的一些文件调用yui compressor,但是忽略了其他需要按原样发送的文件。

\(\)的内部是一个完全匹配build/external的表达式(例如,如果您执行find ./build操作,它将不匹配--在这种情况下,您需要将其更改为./build/external,并且在成功时,将避免遍历下面的任何内容。然后将其分组为带转义括号的单个表达式,并以-not作为前缀,这将使find跳过与该表达式匹配的任何内容。

有人可能会问,添加-not是否不会使-prune隐藏的所有其他文件重新出现,答案是否定的。-prune的工作方式是,一旦到达,该目录下的文件将被永久忽略。

这也很容易扩展以添加额外的排除项。例如:

1
find build -not \( -path build/external -prune \) -not \( -path build/blog -prune \) -name \*.js


对于跳过目录的首选语法应该是什么,这里显然存在一些混淆。

GNU意见

1
To ignore a directory and the files under it, use -prune

从GNU查找手册页

推理

-prune停止find从下降到目录中。仅指定-not -path仍将下降到跳过的目录中,但每当find测试每个文件时,-not -path将是错误的。

-prune问题

-prune按照它的意图来做,但是在使用它时仍然需要注意一些事情。

  • find打印修剪后的目录。

    • 的确,这是有意的行为,只是它不会陷入其中。为避免完全打印目录,请使用逻辑上省略该目录的语法。
  • -prune只与-print一起工作,没有其他动作。

    • 不是真的。-prune与除-delete以外的任何行动一起工作。为什么它不能与删除一起工作?为了使-delete工作,查找需要按df顺序遍历目录,因为-delete将首先删除叶,然后删除叶的父目录等…但是要指定-prune才有意义,find需要点击一个目录并停止其下降,这对于-depth-delete上显然没有意义。
  • 性能

    我对这一问题的三个投票最高的答案进行了简单的测试(用-exec bash -c 'echo $0' {} \;替换-print,以展示另一个行动示例)。结果如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    ----------------------------------------------
    # of files/dirs in level one directories
    .performance_test/prune_me     702702    
    .performance_test/other        2        
    ----------------------------------------------

    > find".performance_test" -path".performance_test/prune_me" -prune -o -exec bash -c 'echo"$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 23513814

    > find".performance_test" -not \( -path".performance_test/prune_me" -prune \) -exec bash -c 'echo"$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 10670141

    > find".performance_test" -not -path".performance_test/prune_me*" -exec bash -c 'echo"$0"' {} \;
    .performance_test
    .performance_test/other
    .performance_test/other/foo
      [# of files] 3 [Runtime(ns)] 864843145

    结论

    F10bit的语法和DanielC.Sobral的语法平均需要10-25毫秒。getfree的语法不使用-prune,使用了865毫秒。所以,是的,这是一个非常极端的例子,但是如果您关心运行时间,并且正在做一些远程密集的工作,那么应该使用-prune

    注意:Daniel C.Sobral的语法在两个-prune语法中表现得更好;但是,我强烈怀疑这是某些缓存的结果,因为切换两个运行的顺序会导致相反的结果,而非prune版本总是最慢。

    测试脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    #!/bin/bash

    dir='.performance_test'

    setup() {
      mkdir"$dir" || exit 1
      mkdir -p"$dir/prune_me/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/w/x/y/z" \
       "$dir/other"

      find"$dir/prune_me" -depth -type d -exec mkdir '{}'/{A..Z} \;
      find"$dir/prune_me" -type d -exec touch '{}'/{1..1000} \;
      touch"$dir/other/foo"
    }

    cleanup() {
      rm -rf"$dir"
    }

    stats() {
      for file in"$dir"/*; do
        if [[ -d"$file" ]]; then
          count=$(find"$file" | wc -l)
          printf"%-30s %-10s
    ""$file""$count"
        fi
      done
    }

    name1() {
      find"$dir" -path"$dir/prune_me" -prune -o -exec bash -c 'echo"$0"'  {} \;
    }

    name2() {
      find"$dir" -not \( -path"$dir/prune_me" -prune \) -exec bash -c 'echo"$0"' {} \;
    }

    name3() {
      find"$dir" -not -path"$dir/prune_me*" -exec bash -c 'echo"$0"' {} \;
    }

    printf"Setting up test files...

    "
    setup
    echo"----------------------------------------------"
    echo"# of files/dirs in level one directories"
    stats | sort -k 2 -n -r
    echo"----------------------------------------------"

    printf"
    Running performance test...

    "

    echo \> find ""$dir"" -path ""$dir/prune_me"" -prune -o -exec bash -c \'echo "\$0"\'  {} \\\;
    name1
    s=$(date +%s%N)
    name1_num=$(name1 | wc -l)
    e=$(date +%s%N)
    name1_perf=$((e-s))
    printf"  [# of files] $name1_num [Runtime(ns)] $name1_perf

    "

    echo \> find ""$dir"" -not \\\( -path ""$dir/prune_me"" -prune \\\) -exec bash -c \'echo "\$0"\' {} \\\;
    name2
    s=$(date +%s%N)
    name2_num=$(name2 | wc -l)
    e=$(date +%s%N)
    name2_perf=$((e-s))
    printf"  [# of files] $name2_num [Runtime(ns)] $name2_perf

    "

    echo \> find ""$dir"" -not -path ""$dir/prune_me*"" -exec bash -c \'echo "\$0"\' {} \\\;
    name3
    s=$(date +%s%N)
    name3_num=$(name3 | wc -l)
    e=$(date +%s%N)
    name3_perf=$((e-s))
    printf"  [# of files] $name3_num [Runtime(ns)] $name3_perf

    "

    echo"Cleaning up test files..."
    cleanup


    一个选项是排除包含grep目录名的所有结果。例如:

    1
    find . -name '*.js' | grep -v excludeddir


    这是唯一为我工作的。

    1
    find / -name NameOfFile ! -path '*/Directory/*'

    正在搜索"nameoffile",不包括"directory"。把重点放在星星上。


    我更喜欢用-not符号…更具可读性:

    1
    find . -name '*.js' -and -not -path directory


    使用-prune选项。所以,有点像:

    1
    find . -type d -name proc -prune -o -name '*.js'

    "-type d-name proc-prune"只查找名为proc的要排除的目录。"-o"是"或"运算符。


    这是我用来排除某些路径的格式:

    1
    $ find ./ -type f -name"pattern" ! -path"excluded path" ! -path"excluded path"

    我用它查找不在".*"路径中的所有文件:

    1
    $ find ./ -type f -name"*" ! -path"./.*" ! -path"./*/.*"


    -prune肯定有效,是最好的答案,因为它可以防止下降到要排除的目录中。-not -path仍在搜索排除的目录,它只是不打印结果,如果排除的目录已装入网络卷或您不允许,这可能是一个问题。

    棘手的部分是,find对参数的顺序非常特别,因此如果您不能正确地得到它们,您的命令可能无法工作。论据的顺序一般如下:

    1
    find {path} {options} {action}

    {path}:把所有与路径相关的参数放在第一位,如. -path './dir1' -prune -o

    {options}:我把-name, -iname, etc作为这个组的最后一个选项时最成功。如-type f -iname '*.js'

    {action}:使用-prune时要加-print

    下面是一个工作示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # setup test
    mkdir dir1 dir2 dir3
    touch dir1/file.txt; touch dir1/file.js
    touch dir2/file.txt; touch dir2/file.js
    touch dir3/file.txt; touch dir3/file.js

    # search for *.js, exclude dir1
    find . -path './dir1' -prune -o -type f -iname '*.js' -print

    # search for *.js, exclude dir1 and dir2
    find . \( -path './dir1' -o -path './dir2' \) -prune -o -type f -iname '*.js' -print

    -path-prune方法也适用于路径中的通配符。下面是一个find语句,它将查找为多个git库提供服务的git服务器的目录,而不包括git内部目录:

    1
    2
    3
    4
    5
    6
    7
    8
    find . -type d \
       -not \( -path */objects -prune \) \
       -not \( -path */branches -prune \) \
       -not \( -path */refs -prune \) \
       -not \( -path */logs -prune \) \
       -not \( -path */.git -prune \) \
       -not \( -path */info -prune \) \
       -not \( -path */hooks -prune \)

    有很多好的答案,我只是花了一些时间来理解命令的每个元素的用途和背后的逻辑。

    1
    find . -path ./misc -prune -o -name '*.txt' -print

    find将开始在当前目录中查找文件和目录,因此是find .

    -o选项代表逻辑"或",并将命令的两部分分开:

    1
    [ -path ./misc -prune ] OR [ -name '*.txt' -print ]

    任何不是./misc目录的目录或文件都不会通过第一个测试-path ./misc。但它们将根据第二个表达式进行测试。如果它们的名称对应于*.txt模式,则它们将被打印,因为-print选项。

    当find到达./misc目录时,该目录只满足第一个表达式。因此,-prune选项将适用于它。它告诉find命令不要浏览该目录。因此,/misc中的任何文件或目录都不会被find浏览,也不会根据表达式的第二部分进行测试,也不会被打印出来。


    要排除多个目录:

    1
    find . -name '*.js' -not \( -path"./dir1" -o -path"./dir2/*" \)

    要添加目录,请添加-o -path"./dirname/*"

    1
    find . -name '*.js' -not \( -path"./dir1" -o -path"./dir2/*" -o -path"./dir3/*"\)

    但是,如果要排除的目录很多,也许应该使用正则表达式。


    对于工作溶液(在Ubuntu 12.04(精确穿山甲)上测试)。

    1
    find ! -path"dir1" -iname"*.mp3"

    将在当前文件夹和子文件夹中搜索MP3文件,dir1子文件夹除外。

    用途:

    1
    find ! -path"dir1" ! -path"dir2" -iname"*.mp3"

    …排除dir1和dir2


    您可以使用prune选项来实现这一点。例如:

    1
    find ./ -path ./beta/* -prune -o -iname example.com -print

    或相反的grep"grep-v"选项:

    1
    find -iname example.com | grep -v beta

    您可以在Linux中找到详细的说明和示例find命令从搜索中排除目录。


    1
    find -name '*.js' -not -path './node_modules/*' -not -path './vendor/*'

    似乎和

    1
    find -name '*.js' -not \( -path './node_modules/*' -o -path './vendor/*' \)

    而且在我看来更容易记住。


    对于那些在旧版本的Unix上不能使用-path或-not的用户

    在Sunos 5.10 bash 3.2和Sunos 5.11 bash 4.4上测试

    1
    find . -type f -name"*" -o -type d -name"*excluded_directory*" -prune -type f


    我使用findxgettext提供一个文件列表,并想省略一个特定的目录及其内容。我试了很多种方法,把-path-prune结合起来,但不能完全排除我想删除的目录。

    虽然我可以忽略我想要忽略的目录的内容,但是find随后返回了目录本身作为结果之一,这导致xgettext崩溃(不接受目录,只接受文件)。

    我的解决方案是使用grep -v跳过我不希望在结果中出现的目录:

    1
    find /project/directory -iname '*.php' -or -iname '*.phtml' | grep -iv '/some/directory' | xargs xgettext

    我不能肯定,是否有一个论据支持100%有效的find。头痛过后,使用grep是一种快速而容易的解决方法。


    以前的答案在Ubuntu上都不好。试试这个:

    1
    find . ! -path"*/test/*" -type f -name"*.js" ! -name"*-min-*" ! -name"*console*"

    我在这里找到这个


    1
    find . -name '*.js' -\! -name 'glob-for-excluded-dir' -prune


    这适合我在Mac电脑上使用:

    1
    find . -name *.php -or -path"./vendor" -prune -or -path"./app/cache" -prune

    它将不包括vendorapp/cachedir作为后缀为php的搜索名称。


    对于我所需要的,它是这样工作的,从根目录开始在所有服务器中查找landscape.jpg,不包括/var目录中的搜索:

    find / -maxdepth 1 -type d | grep -v /var | xargs -I '{}' find '{}' -name landscape.jpg

    find / -maxdepth 1 -type d列出/中的所有目录

    grep -v /var从列表中排除了`/var'

    xargs -I '{}' find '{}' -name landscape.jpg对list中的每个目录/结果执行任何命令,如find


    如何在sh中使用find的prune选项是劳伦斯·冈萨尔维斯关于-prune如何工作的一个很好的答案。

    下面是通用的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    find /path/to/search                    \
      -type d                               \
        \( -path /path/to/search/exclude_me \
           -o                               \
           -name exclude_me_too_anywhere    \
         \)                                 \
        -prune                              \
      -o                                    \
      -type f -name '*\.js' -print

    为了避免多次输入/path/to/seach/,请将find包装在pushd .. popd对中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pushd /path/to/search;                  \
    find .                                  \
      -type d                               \
        \( -path ./exclude_me               \
           -o                               \
           -name exclude_me_too_anywhere    \
         \)                                 \
        -prune                              \
      -o                                    \
      -type f -name '*\.js' -print;         \
     popd

    这里已经有很多答案了;我不想再加一个,但我认为这个信息是有用的。

    tldr:使用"-prune"选项,了解您的根目录并从中定制搜索。

    背景:我有一个rsnapshot(rsync备份位置,/mnt/Backups/在搜索系统(/文件时会引起头痛,因为这些备份包含大约4.5tb(terra)的文件!

    我也有/mnt/Vancouver,我的主要工作文件夹和tb文件,备份了[/mnt/Backups//mnt/Vancouver/物理(冗余)安装在单独的驱动器上]。

    在这里的两个答案中(如何排除查找中的目录)。命令),我发现使用已接受的答案搜索系统文件更快,但需要注意。

    这一个

    1
    find / -path /mnt -prune -o -name"*libname-server-2.a*" -print

    在~3-4秒内找到该文件;此文件

    1
    find / -name"*libname-server-2.a*" -not -path"/mnt/*"

    出现(?)要在所有排除的目录中重复出现(所有已装入卷的深度嵌套的rsync快照),这需要永远。我假设它正在搜索多TB的文件,所以它陷入了无休止的困境。例如,如果我试图"计时"搜索(time find ...),我会看到大量的输出——这表明find正在深入遍历"排除"目录:

    1
    2
    3
    ...
    find: ‘/mnt/Backups/rsnapshot_backups/monthly.0/snapshot_root/var/lib/udisks2’: Permission denied
    ...

    在排除的目录(/mnt/或嵌套的路径(`/mnt/backups')后面加上一个正斜杠将导致再次搜索*永远:

    慢:

    1
    2
    find / -path /mnt/ -prune -o -name"*libname-server-2.a*" -print
    find / -path /mnt/Vancouver -prune -o -name"*libname-server-2.a*" -print

    "解决方案"

    这里是最好的解决方案(所有这些都在几秒钟内执行)。同样,我的目录结构是

    • /
    • /mnt/Backups/:多TB备份
    • /mnt/Vancouver/:多TB工作目录(备份到单独驱动器上的/mnt/Backups),我经常想搜索它
    • /home/*:其他安装点/工作"驱动器"(如/home/victoria=~)

    系统文件(/):

    要快速查找系统文件,请排除/mnt(不是/mnt//mnt/Backups…):

    1
    2
    $ find / -path /mnt -prune -o -name"*libname-server-2.a*" -print
    /usr/lib/libname-server-2.a

    在3-4秒内找到那个文件。

    非系统文件:

    例如,要在我的两个工作"驱动器"之一中快速定位文件,/mnt/Vancouver/和/或/home/victoria/

    1
    2
    3
    4
    5
    6
    $ find /mnt/Vancouver/ -name"*04t8ugijrlkj.jpg"
    /mnt/Vancouver/temp/04t8ugijrlkj.jpg

    $ find /home/victoria -iname"*Untitled Document 1"
    /home/victoria/backups/shortcuts.bak.2016.11.02/Untitled Document 1
    /home/victoria/Untitled Document 1

    备份:

    例如,在我的每小时/每天/每周/每月备份中查找已删除的文件)。

    1
    2
    $ find /mnt/Backups/rsnapshot_backups/daily.0 -name"*04t8ugijrlkj.jpg"
    /mnt/Backups/rsnapshot_backups/daily.0/snapshot_root/mnt/Vancouver/temp/04t8ugijrlkj.jpg

    旁白:在命令末尾添加-print将取消排除目录的打印输出:

    1
    2
    3
    4
    5
    6
    7
    $ find / -path /mnt -prune -o -name"*libname-server-2.a*"
    /mnt
    /usr/lib/libname-server-2.a

    $ find / -path /mnt -prune -o -name"*libname-server-2.a*" -print
    /usr/lib/libname-server-2.a
    $


    最好使用exec动作,而不是for循环:

    1
    2
    find . -path"./dirtoexclude" -prune \
        -o -exec java -jar config/yuicompressor-2.4.2.jar --type js '{}' -o '{}' \;

    每个匹配文件执行一次exec ... '{}' ... '{}' \;,用当前文件名替换大括号'{}'

    请注意,大括号括在单引号中,以保护它们不被解释为shell脚本标点*。

    笔记

    *从find (GNU findutils) 4.4.2手册页的示例部分


    我试过上面的命令,但是那些使用"-prune"的命令都不适合我。最后我用下面的命令尝试了这个方法:

    1
    find . \( -name"*" \) -prune -a ! -name"directory"

    我在C源文件中找到了函数名exclude*.o和exclude*.swp和exclude(非常规文件),并使用以下命令排除dir输出:

    1
    find .  \( ! -path"./output/*" \) -a \( -type f \) -a \( ! -name '*.o' \) -a \( ! -name '*.swp' \) | xargs grep -n soc_attach

    这是因为find测试模式"*foo*的文件:

    1
    find ! -path"dir1" ! -path"dir2" -name"*foo*"

    但如果不使用模式(find不测试文件),它就不起作用。因此,find没有利用它以前评估的"真"和"假"的bools。使用上述符号的不工作用例示例:

    1
    find ! -path"dir1" ! -path"dir2" -type f

    没有find测试!因此,如果需要查找没有任何模式匹配的文件,请使用-prune。此外,通过使用prune find总是更快,但它确实跳过了目录,而不是匹配它,或者更好的是不匹配它。因此,在这种情况下,请使用如下内容:

    1
    find dir -not \( -path"dir1" -prune \) -not \( -path"dir2" -prune \) -type f

    或:

    1
    find dir -not \( -path"dir1" -o -path"dir2" -prune \) -type f

    当做


    对于FreeBSD用户:

    1
     find . -name '*.js' -not -path '*exclude/this/dir*'


    如果搜索目录有模式(在我的情况下,大多数情况下);您可以简单地如下操作:

    1
    find ./n* -name"*.tcl"

    在上面的例子中,它搜索所有以"n"开头的子目录。


    我在这个页面上找到了建议,其他很多页面在我的Mac OS X系统上都不起作用。但是,我发现了一个适合我的变体。

    最大的想法是搜索Macintosh HD,但避免遍历所有外部卷,这些外部卷主要是时间机器备份、映像备份、装入的共享和存档,但不必全部卸载它们,这通常是不切实际的。

    这是我的工作脚本,我把它命名为"findit"。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/usr/bin/env bash
    # inspired by http://stackoverflow.com/questions/4210042/exclude-directory-from-find-command Danile C. Sobral
    # using special syntax to avoid traversing.
    # However, logic is refactored because the Sobral version still traverses
    # everything on my system

    echo ============================
    echo find - from cwd, omitting external volumes
    date
    echo Enter sudo password if requested
    sudo find . -not \( \
    -path ./Volumes/Archive -prune -o \
    -path ./Volumes/Boot\ OS\ X -prune -o \
    -path ./Volumes/C \
    -path ./Volumes/Data -prune -o \
    -path ./Volumes/jas -prune -o \
    -path ./Volumes/Recovery\ HD -prune -o \
    -path ./Volumes/Time\ Machine\ Backups -prune -o \
    -path ./Volumes/SuperDuper\ Image -prune -o \
    -path ./Volumes/userland -prune \
    \) -name"$1" -print
    date
    echo ============================
    iMac2:~ jas$

    各种路径与外部存档卷、时间机器、虚拟机、其他已装入的服务器等有关。有些卷名中有空格。

    一个好的测试运行是"findit index.php",因为该文件出现在我的系统中的许多地方。使用此脚本,搜索主硬盘大约需要10分钟。如果没有这些例外,就需要很多小时。


    不确定这是否能覆盖所有的边缘情况,但下面将是非常直接和简单的尝试:

    ls -1|grep -v -e ddl -e docs| xargs rm -rf

    这应该从当前目录中删除所有文件/目录,不包括"ddls"和"docs"。


    我想知道目录的数量,文件的大小,以及当前目录的大小,而这段代码正是我想要的:—)

    源头

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - ...    2791037 Jun  2  2011 foo.jpg
    - ... 1284734651 M?r 10 16:16 foo.tar.gz
    - ...          0 M?r 10 15:28 foo.txt
    d ...       4096 M?r  3 17:12 HE
    d ...       4096 M?r  3 17:21 KU
    d ...       4096 M?r  3 17:17 LE
    d ...          0 M?r  3 17:14 NO
    d ...          0 M?r  3 17:15 SE
    d ...          0 M?r  3 17:13 SP
    d ...          0 M?r  3 17:14 TE
    d ...          0 M?r  3 19:20 UN

    代码

    1
    2
    3
    4
    5
    6
    format="%s%'12d
    "

    find . -type d -not -path"./*/*" | wc -l | awk -v fmt=$format '{printf fmt," Anzahl Ordner  =", $1-1}'
    find . -type f -not -path"./*/*" | wc -l | awk -v fmt=$format '{printf fmt," Anzahl Dateien =", $1}'
      du . -hmS --max-depth=0 | awk -v fmt=$format '{printf fmt," Groesse (MB)   =", $1}'

    注:awk需要额外的format="%s%'12d
    "
    来格式化数字。

    结果

    1
    2
    3
    Anzahl Ordner  =            8
    Anzahl Dateien =            3
    Groesse (MB)   =        1.228