如何在 find 中排除目录。命令

我正在尝试对所有 JavaScript 文件运行find命令,但是如何排除特定目录?

这是我们正在使用的find代码。

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

答案

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

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

警告:需要遍历所有不需要的目录。

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

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

这是带有多个目录的示例:

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

在这里我们排除了dir1dir2dir3 ,因为在find表达式中,这是一个作用于条件-path dir1 -o -path dir2 -o -path dir3 (如果dir1dir2dir3 )的操作,并与type -d

进一步的操作是-o print ,仅打印。

与其他提议的解决方案相比,我发现以下原因更容易推理:

find build -not \( -path build/external -prune \) -name \*.js
# you can also exclude multiple paths
find build -not \( -path build/external -prune \) -not \( -path build/blog -prune \) -name \*.js

重要说明:-path之后键入的路径必须与find结果完全匹配,且不排除find结果。如果这句话使您感到困惑,则只需确保在整个命令中使用完整路径,如下所示: find <strong>/full/path/</strong> -not \( -path <strong>/full/path/exclude/this</strong> -prune \) ...如果您想更好地理解,请参见注释 [1]。

\(\)内部是一个表达式,该表达式将完全匹配build/external (请参见上面的重要说明),并且在成功后将避免遍历下面的任何内容 。然后,将其作为带有转义括号的单个表达式分组,并以-not前缀,这将使find跳过该表达式匹配的任何内容。

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

这来自一个实际的用例,在该用例中,我需要对 Wintersmith 生成的某些文件调用 yui-compressor,但不包括需要按原样发送的其他文件。


注意 [1] :如果要排除/tmp/foo/bar并运行像这样的find /tmp \(...find /tmp \(... ”,那么必须指定-path /tmp/foo/bar 。您运行像cd /tmp; find . \(...然后必须指定-path ./foo/bar

对于跳过目录的首选语法,这里显然存在一些困惑。

GNU 意见

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

从 GNU 查找手册页

推理

-prune停止从find到目录的find 。仅指定-not -path仍会进入跳过的目录,但只要find对每个文件进行测试,则-not -path将为 false。

-prune问题

-prune可以达到预期的目的,但是在使用它时仍需要注意一些事项。

  1. find打印修剪后的目录。

    • 诚然 ,公司预期的行为,它只是不落在其中。为避免完全打印目录,请使用在逻辑上将其省略的语法。
  2. -prune仅可与-print而不能执行其他任何操作。

    • 不正确-prune可以执行除-delete之外的任何操作。 为什么删除不起作用?为了使-delete正常工作,find 需要以 DFS 顺序遍历目录,因为-delete首先删除叶子,然后是叶子的父级,等等... 但是为了使-prune有意义, find需要打一个目录并停止降序,使用-depth-delete显然没有任何意义。

性能

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

----------------------------------------------
# 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 的语法Daniel C. Sobral 的语法平均需要 10 到 25 毫秒才能运行。不使用-prune GetFree 语法耗时 865 毫秒。因此,是的,这是一个非常极端的示例,但是如果您关心运行时并且正在做任何远程密集型工作,则应该使用-prune

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

测试脚本

#!/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\n" "$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...\n\n"
setup
echo "----------------------------------------------"
echo "# of files/dirs in level one directories"
stats | sort -k 2 -n -r
echo "----------------------------------------------"

printf "\nRunning performance test...\n\n"

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\n\n"

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\n\n"

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\n\n"

echo "Cleaning up test files..."
cleanup

这是唯一为我工作的人。

find / -name MyFile ! -path '*/Directory/*'

搜索除 “目录” 外的 “MyFile”。强调星星 *。

一种选择是使用 grep 排除所有包含目录名称的结果。例如:

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

我更喜欢使用-not表示法... 更具可读性:

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

使用 - prune 选项。因此,类似:

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

“-d -name proc -prune” 仅查找要排除的名为 proc 的目录。
“-o” 是 “OR” 运算符。

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

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

我用它来查找不在 “。*” 路径中的所有文件:

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

-prune绝对有效,并且是最佳答案,因为它可以防止下降到要排除的目录中。 -not -path仍在搜索排除的目录,只是不打印结果,如果排除的目录已装入网络卷或您没有权限,则可能会出现问题。

棘手的部分是find对于参数的顺序非常严格,因此,如果您输入的参数不正确,则命令可能无法正常工作。参数的顺序通常如下:

find {path} {options} {action}

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

{options} :将-name, -iname, etc作为该组的最后一个选项时-name, -iname, etc我获得最大的成功。例如-type f -iname '*.js'

{action} :使用-prune时,您需要添加-print

这是一个工作示例:

# 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