使用 find 仅查找前几个匹配的文件

使用 find 仅查找前几个匹配的文件

*.txt假设一个目录中有数百个文件。我只想找到前三个*.txt文件,然后退出搜索过程。

如何使用该find实用程序实现此目的?我快速浏览了它的手册页,但没有看到任何选项。

答案1

find您可以通过管道输出head

find . -name '*.txt' | head -n 3

答案2

这个另一个答案是有些缺陷的。命令是

find . -name '*.txt' | head -n 3

然后有一个解释在其中一篇评论中[强调我的]:

head启动并等待来自管道左侧的输入。然后find启动并搜索与指定条件匹配的文件,通过管道发送其输出。当head收到并打印所请求的行数时,它终止,关闭管道。find注意到关闭的管道并且它也终止。简约、优雅、高效的

这是几乎真的。

问题是find只有在尝试写入时才会注意到关闭的管道 - 在本例中是在找到第四个匹配项时。但如果没有第四场比赛,则find继续进行。你的外壳会等待!如果它发生在脚本中,脚本将等待,尽管我们已经知道管道输出是最终的并且不能向其中添加任何内容。效率不高。

find如果此特定操作本身快速完成,但在大型文件树中进行复杂搜索,则该命令可能会不必要地延迟您下一步想要执行的操作,则效果可以忽略不计。

不太完美的解决方案是运行

( find … & ) | head -n 3

这样head退出时,shell 会立即继续。后台find进程可能会被忽略(它迟早会退出)或以pkill其他方式为目标。


为了证明这个概念,您可能会搜索/。我们只期望一场比赛,但却find到处寻找,这可能会花费很多时间。

find / -name / 2>/dev/null | head -n 1

一旦发现问题,请立即使用Ctrl+终止它。C现在比较一下:

pidof find ; ( find / -name / 2>/dev/null & ) | head -n 1 ; pidof find

更好的解决方案可能是:

yes | head -n 2 \
| find … -print -exec sh -c '
   read dummy || kill -s PIPE "$PPID"
' find-sh \;

笔记:

  • 这里我们想要 3 个匹配的文件,但我们使用head -n 2(not head -n 3)。在第三个匹配文件之后,read在其标准输入上找不到输入,然后kill终止find。如果我们使用head -n 3, thenkill将会在第四个文件之后触发。

  • 信号是SIGPIPEkill -s INT …应该也能工作。我特意选择了它,因为它是在最简单的解决方案 ( ) 中SIGPIPE终止的信号。findfind … | head -n 3

  • 如果您需要 3 个文件,则每个匹配文件运行一个sh可以忽略不计。请记住,我们的目标是避免这种情况find(我称之为“不太完美的解决方案”)在后台徒劳地运行;对于操作系统的整体性能来说,肯定没有几个短暂的 shell 比“废弃”的find遍历文件系统更好。但是,如果您想要(最多)1000 个文件,并且很可能find会更早地用完文件(因此我们可能不想避免任何问题),那么这些 shell 就是一种负担。

    以下代码产生减少的sh进程数量,但我认为它是有缺陷的:

    # flawed, DO NOT USE
    yes | head -n 999 \
    | find … -exec sh -c '
       for pathname do
          printf "%s\\n" "$pathname"
          read dummy || { kill -s PIPE "$PPID"; exit 0; }
       done
    ' find-sh {} +
    

    我必须将-print(从 shell 代码外部)替换为printf …(在 shell 代码内部)。原因是-print之前-exec sh … {} +可以(并且可能会)打印太多路径名。

    一个潜在的问题出现了:如果每个人都printf创建一个单独的进程,那么就会使这种“优化”变得毫无意义。幸运的是,几乎(?)每个sh printf都是内置的。

    但真正的缺陷是exec sh … {} +在将路径名交给 之前会等待尽可能多的路径名sh。一方面,这正是减少sh进程数量的原因。另一方面,几乎可以肯定的是,当第 1000 个匹配项入队时,find将继续搜索第 1001 个;当第 1001 个被发现时,可能会发现更多。请注意,在这种情况下,第 1001 场比赛将终止find … | head -n 1000;所以这个有缺陷的解决方案比最简单的解决方案还要糟糕,不要使用它。

  • find … | head -n 3如果打印的路径名之一中有换行符,最简单的解决方案 ( ) 将会错误计数。如果您想要以 null 结尾的字符串,那么最简单的解决方案将变得像这样find … -print0 | head -z -n 3,即您将需要head支持此不可移植选项-z。在我们的优化解决方案中,您既不需要也不head -z需要find -print0printf "%s\\0" "$pathname"在 shell 代码中就足够了。

  • 计数是sh通过消耗从 继承的 stdin 中的行在内部完成的find。通常,您不会将任何内容通过管道传输到find,但一般来说,您可能出于计数以外的其他目的这样做。那么其他目的和我们的计数方法就会不兼容。

  • yes不便于携带。我们的目的while :; do echo; done是便携式替代品。

  • find-sh解释如下:中的第二个 sh 是什么sh -c 'some shell code' sh


一位用户要求提供一个实现该解决方案的 shell 函数。这里是:

findn () (
  n="$1"
  shift
  case "$n" in
    '' | *[!0123456789]*) echo >&2 not a valid number; 
  exit 1;;
  esac
  [ "$n" -eq 0 ] && exit 0
  n="$((n-1))"
  while :; do echo; done | head -n "$n" \
  | find "$@" -exec sh -c '
     read dummy || kill -s PIPE "$PPID"
  ' find-sh \;
)

第一个参数是你想要的最大匹配数,其余的将交给find。笔记:

用法示例:

findn 2 / -name bin -print 2>/dev/null

答案3

如果没有这个解决方案find,可能对很多人都有效fd,那就是使用用 Rust 编写的类似查找的工具。 (fd 是一种简单、快速且用户友好的 find 替代方案

fd --glob '*.txt' /path/to/search --max-results $n

答案4

使用bash4.4+ 和 GNU 工具,要在找到第三个文件后尽早退出,您可以执行以下操作:

n=3
readarray -td '' first_3_files < <(
  (
    echo "$BASHPID"
    LC_ALL=C exec stdbuf -o0 find . -name '*.txt' -type f -print0
  ) | {
    IFS= read -r pid
    head -zn "$n"
    kill -s PIPE "$pid"
  }
)

echo "The first $n files are:"
printf ' - %s\n' "${first_3_files[@]}"

stdbuf -o0停止缓冲其输出,并且我们一返回find就发送 SIGPIPE 信号,而不是让继续搜索并仅在找到并打印第四个文件路径时接收 SIGPIPE。findhead -zn 3find

或者使用 GNU 谓词的另一种 GNU 特定find方法-quit

n=3
readarray -td '' first_3_files < <(
  seq "$((n - 1))" | LC_ALL=C find . -name '*.txt' -type f -print0 \
   ! -exec read iteration ';' -quit)

(如果您的系统没有独立read实用程序,请使用-exec sh -c 'read iteration' ';';具有独立read实用程序的系统可能已将其实现为围绕内置程序的 shell 脚本包装器read)。

使用zsh,您可以执行以下操作:

first_3_files=( **/*.txt(ND.Y3) )

相关内容