x=$(find . -name "*.txt")
echo $x
Run Code Online (Sandbox Code Playgroud)
如果我在Bash shell中运行上面的代码,我得到的是一个包含多个文件名的字符串,用空格分隔,而不是列表.
当然,我可以进一步将它们分开来获取列表,但我确信有更好的方法可以做到这一点.
那么循环find执行命令结果的最佳方法是什么?
Kev*_*vin 329
TL; DR:如果你只是在这里找到最正确的答案,你可能想要我个人的偏好,find . -name '*.txt' -exec process {} \;(见本文的底部).如果你有时间,请仔细阅读其余内容,看看几种不同的方式以及大多数问题.
完整答案:
最好的方法取决于你想做什么,但这里有一些选择.只要子树中的文件或文件夹名称中没有空格,您就可以循环遍历文件:
for i in $x; do # Not recommended, will break on whitespace
process "$i"
done
Run Code Online (Sandbox Code Playgroud)
在边缘更好,切出临时变量x:
for i in $(find -name \*.txt); do # Not recommended, will break on whitespace
process "$i"
done
Run Code Online (Sandbox Code Playgroud)
如果可以的话,它会更好.白色空间安全,适用于当前目录中的文件:
for i in *.txt; do # Whitespace-safe but not recursive.
process "$i"
done
Run Code Online (Sandbox Code Playgroud)
通过启用该globstar选项,您可以对此目录和所有子目录中的所有匹配文件进行选通:
# Make sure globstar is enabled
shopt -s globstar
for i in **/*.txt; do # Whitespace-safe and recursive
process "$i"
done
Run Code Online (Sandbox Code Playgroud)
在某些情况下,例如,如果文件名已经在文件中,您可能需要使用read:
# IFS= makes sure it doesn't trim leading and trailing whitespace
# -r prevents interpretation of \ escapes.
while IFS= read -r line; do # Whitespace-safe EXCEPT newlines
process "$line"
done < filename
Run Code Online (Sandbox Code Playgroud)
readfind通过适当设置分隔符可以安全地使用:
find . -name '*.txt' -print0 |
while IFS= read -r -d '' line; do
process $line
done
Run Code Online (Sandbox Code Playgroud)
对于更复杂的搜索,您可能希望使用find其-exec选项或使用-print0 | xargs -0:
# execute `process` once for each file
find . -name \*.txt -exec process {} \;
# execute `process` once with all the files as arguments*:
find . -name \*.txt -exec process {} +
# using xargs*
find . -name \*.txt -print0 | xargs -0 process
# using xargs with arguments after each filename (implies one run per filename)
find . -name \*.txt -print0 | xargs -0 -I{} process {} argument
Run Code Online (Sandbox Code Playgroud)
find也可以使用-execdir而不是在运行命令之前cd进每个文件的目录-exec,并且可以使用-ok而不是-exec(或-okdir代替-execdir)使其成为交互式(在为每个文件运行命令之前提示).
*:从技术上讲,两者find和xargs(默认情况下)将使用尽可能多的参数来运行命令,因为它们可以在命令行中使用,这是完成所有文件所需的次数.在实践中,除非你有非常多的文件,否则无关紧要,如果你超过了长度但是需要在同一个命令行上,你就是SOL找到了另一种方式.
0xC*_*22L 97
find . -name "*.txt"|while read fname; do
echo "$fname"
done
Run Code Online (Sandbox Code Playgroud)
注意:此方法和 bmargulies显示的(第二)方法可以安全地与文件/文件夹名称中的空格一起使用.
为了使文件/文件夹名称中包含换行符 - 有点奇特 - ,您将不得不求助于这样的-exec谓词find:
find . -name '*.txt' -exec echo "{}" \;
Run Code Online (Sandbox Code Playgroud)
它{}是找到的项的占位符,\;用于终止-exec谓词.
为了完整起见,让我添加另一种变体 - 你必须喜欢*nix的方式来实现它们的多功能性:
find . -name '*.txt' -print0|xargs -0 -n 1 echo
Run Code Online (Sandbox Code Playgroud)
\0根据我的知识,这会将打印的项目与文件或文件夹名称中的任何文件系统中不允许的字符分开,因此应涵盖所有基础.xargs然后一个接一个地把它们拿起......
Dav*_* W. 97
你做什么,不要使用for循环:
# Don't do this
for file in $(find . -name "*.txt")
do
…code using "$file"
done
Run Code Online (Sandbox Code Playgroud)
三个原因:
find必须运行完成.for循环返回40KB的文本.最后8KB将从你的for循环中删除,你永远不会知道它.始终使用while read构造:
find . -name "*.txt" -print0 | while read -d $'\0' file
do
…code using "$file"
done
Run Code Online (Sandbox Code Playgroud)
循环将在find命令执行时执行.此外,即使返回带有空格的文件名,此命令也会起作用.并且,您不会溢出命令行缓冲区.
在-print0将使用NULL作为文件分隔符,而不是换行和-d $'\0'边阅读将使用NULL作为分隔符.
Mic*_*rux 13
文件名可以包含空格甚至控制字符.空格是bash中shell扩展的(默认)分隔符,因此x=$(find . -name "*.txt")根本不推荐使用该问题的结果.如果find获取带有空格的文件名,例如"the file.txt",如果您x在循环中处理,则将获得2个用于处理的字符串.您可以通过更改分隔符(bash IFS变量)来改进这一点,例如\r\n,但文件名可以包含控制字符 - 因此这不是(完全)安全的方法.
从我的角度来看,有2种推荐(和安全)模式用于处理文件:
1.用于循环和文件名扩展:
for file in ./*.txt; do
[[ ! -e $file ]] && continue # continue, if file does not exist
# single filename is in $file
echo "$file"
# your code here
done
Run Code Online (Sandbox Code Playgroud)
2.使用find-read-while和process替换
while IFS= read -r -d '' file; do
# single filename is in $file
echo "$file"
# your code here
done < <(find . -name "*.txt" -print0)
Run Code Online (Sandbox Code Playgroud)
备注
在模式1:
nullglob可用于避免这个额外的行.failglob设置了shell选项,但未找到匹配项,则会打印一条错误消息,并且不会执行该命令." (来自上面的Bash手册)globstar:"如果设置,文件名扩展上下文中使用的模式'**'将匹配所有文件和零个或多个目录和子目录.如果模式后跟'/',则只有目录和子目录匹配." 参见Bash手册,Shopt Builtinextglob,nocaseglob,dotglob及壳可变GLOBIGNORE 在模式2:
文件名可包含空格,制表符,空格,新行,...以安全的方式来处理文件名,find与-print0使用:文件名是印有所有的控制字符和与NUL终止.另请参阅Gnu Findutils手册页,不安全文件名处理, 安全文件名处理,文件名中的异常字符.有关此主题的详细讨论,请参阅下面的David A. Wheeler.
有一些可能的模式可以在while循环中处理查找结果.其他人(凯文,大卫W.)已经展示了如何使用管道做到这一点:
files_found=1
find . -name "*.txt" -print0 |
while IFS= read -r -d '' file; do
# single filename in $file
echo "$file"
files_found=0 # not working example
# your code here
done
[[ $files_found -eq 0 ]] && echo "files found" || echo "no files found"
当你尝试这段代码时,你会发现它不起作用:files_found总是"真实"并且代码将始终回显"找不到文件".原因是:管道的每个命令都在一个单独的子shell中执行,因此循环内部更改的变量(单独的子shell)不会更改主shell脚本中的变量.这就是为什么我建议使用进程替换作为"更好",更有用,更通用的模式.其他参考资料和来源:
我想使用这段代码(在之后通过管道传输命令while done):
while read fname; do
echo "$fname"
done <<< "$(find . -name "*.txt")"
Run Code Online (Sandbox Code Playgroud)
比这个答案更好,因为while循环是根据here在子shell中执行的,如果您使用这个答案while,并且如果您想修改循环内的变量,则在循环后看不到变量更改。
# Doesn't handle whitespace
for x in `find . -name "*.txt" -print`; do
process_one $x
done
or
# Handles whitespace and newlines
find . -name "*.txt" -print0 | xargs -0 -n 1 process_one
Run Code Online (Sandbox Code Playgroud)
如果您可以假设文件名不包含换行符,则可以find使用以下命令将 的输出读入 Bash 数组:
readarray -t x < <(find . -name '*.txt')
Run Code Online (Sandbox Code Playgroud)
笔记:
-t导致readarray剥离换行符。readarray在管道中,它将不起作用,因此过程替换。readarray 从 Bash 4 开始可用。Bash 4.4 及更高版本还支持-d用于指定分隔符的参数。使用空字符而不是换行符来分隔文件名也适用于文件名包含换行符的罕见情况:
readarray -d '' x < <(find . -name '*.txt' -print0)
Run Code Online (Sandbox Code Playgroud)
readarray也可以像mapfile使用相同的选项一样调用。
参考:https : //mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
(已更新,包括@Socowi出色的速度改进)
与任何$SHELL支持它的(破折号/ zsh / bash ...):
find . -name "*.txt" -exec $SHELL -c '
for i in "$@" ; do
echo "$i"
done
' {} +
Run Code Online (Sandbox Code Playgroud)
做完了
原始答案(较短但较慢):
find . -name "*.txt" -exec $SHELL -c '
echo "$0"
' {} \;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
188969 次 |
| 最近记录: |