*.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
(nothead -n 3
)。在第三个匹配文件之后,read
在其标准输入上找不到输入,然后kill
终止find
。如果我们使用head -n 3
, thenkill
将会在第四个文件之后触发。信号是
SIGPIPE
。kill -s INT …
应该也能工作。我特意选择了它,因为它是在最简单的解决方案 ( ) 中SIGPIPE
终止的信号。find
find … | 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 -print0
;printf "%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
。笔记:
原因
case
是这样的:在 Shell 算术评估中使用未经净化的数据的安全影响。运行时
find
,函数会追加-exec …
,因此永远不会存在隐式-print
。如果您希望打印结果,请-print
明确指定。
用法示例:
findn 2 / -name bin -print 2>/dev/null
答案3
如果没有这个解决方案find
,可能对很多人都有效fd
,那就是使用用 Rust 编写的类似查找的工具。 (fd 是一种简单、快速且用户友好的 find 替代方案)
fd --glob '*.txt' /path/to/search --max-results $n
答案4
使用bash
4.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。find
head -zn 3
find
或者使用 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) )