x=$(find . -name "*.txt")
echo $x
如果我在Bash shell中运行上面的代码,我得到的是一个包含多个文件名的字符串,用空格分隔,而不是列表。
当然,我可以进一步将它们分开来获取列表,但我确信有更好的方法可以做到。
那么循环查看find
命令结果的最佳方法是什么?
答案 0 :(得分:298)
TL; DR:如果您只是在这里寻找最正确的答案,您可能需要我的个人偏好find . -name '*.txt' -exec process {} \;
(请参阅本文的底部)。如果你有时间,请仔细阅读其余内容,看看几种不同的方式以及大多数问题。
完整答案:
最好的方法取决于你想做什么,但这里有一些选择。只要子树中的文件或文件夹名称中没有空格,您就可以循环遍历文件:
for i in $x; do # Not recommended, will break on whitespace
process "$i"
done
边缘更好,删除临时变量x
:
for i in $(find -name \*.txt); do # Not recommended, will break on whitespace
process "$i"
done
如果可以的话,它会更好地很多。白色空间安全,适用于当前目录中的文件:
for i in *.txt; do # Whitespace-safe but not recursive.
process "$i"
done
通过启用globstar
选项,您可以对此目录和所有子目录中的所有匹配文件进行选通:
# Make sure globstar is enabled
shopt -s globstar
for i in **/*.txt; do # Whitespace-safe and recursive
process "$i"
done
在某些情况下,例如如果文件名已经在文件中,则可能需要使用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
通过适当设置分隔符, read
可以与find
一起安全使用:
find . -name '*.txt' -print0 |
while IFS= read -r -d '' line; do
process $line
done
对于更复杂的搜索,您可能希望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
在使用find
而不是-execdir
运行命令之前, -exec
也可以进入每个文件的目录,并且可以使用(在为每个文件运行命令之前提示) -ok
代替-exec
(或-okdir
代替-execdir
)。
*:从技术上讲,find
和xargs
(默认情况下)都将使用尽可能多的参数来运行命令,因为它们可以在命令行中使用,所需的次数与通过所有参数的次数相同文件。在实践中,除非你有非常多的文件,否则无关紧要,如果你超过了长度但需要在同一命令行中,你是SOL 找到一种不同的方式。
答案 1 :(得分:91)
find . -name "*.txt"|while read fname; do
echo "$fname"
done
注意:这个方法和 bmargulies显示的(第二个)方法可以安全地用于文件/文件夹名称中的空格。
为了在文件/文件夹名称中包含换行符(有点异国情调),您必须使用-exec
这样的find
谓词:
find . -name '*.txt' -exec echo "{}" \;
{}
是找到的项目的占位符,\;
用于终止-exec
谓词。
为了完整起见,让我添加另一种变体 - 你必须喜欢* nix的方式来实现它们的多功能性:
find . -name '*.txt' -print0|xargs -0 -n 1 echo
根据我的知识,这会将打印的项目与文件或文件夹名称中的任何文件系统中不允许的\0
字符分开,因此应涵盖所有基础。 xargs
逐个接受它们......
答案 2 :(得分:90)
你做了什么,不使用for
循环:
# Don't do this
for file in $(find . -name "*.txt")
do
…code using "$file"
done
三个原因:
find
必须运行完成。for
循环返回40KB的文本。最后8KB将从for
循环中删除,你永远不会知道它。始终使用 while read
构造:
find . -name "*.txt" -print0 | while read -d $'\0' file
do
…code using "$file"
done
循环将在find
命令执行时执行。此外,即使返回带有空格的文件名,此命令也会起作用。并且,您不会溢出命令行缓冲区。
-print0
将使用NULL作为文件分隔符而不是换行符,-d $'\0'
将在读取时使用NULL作为分隔符。
答案 3 :(得分:12)
文件名可以包含空格甚至控制字符。空格是bash中shell扩展的(默认)分隔符,因此根本不推荐使用x=$(find . -name "*.txt")
的问题。如果find获取带有空格的文件名,例如"the file.txt"
如果您在循环中处理x
,您将获得2个分开的字符串进行处理。您可以通过更改分隔符(bash IFS
变量)来改善这一点,例如到\r\n
,但文件名可以包含控制字符 - 所以这不是一个(完全)安全的方法。
从我的角度来看,有两种推荐(和安全)模式用于处理文件:
<强> 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
<强> 2。使用find-read-while&amp;流程替代
while IFS= read -r -d '' file; do
# single filename is in $file
echo "$file"
# your code here
done < <(find . -name "*.txt" -print0)
<强>说明强>
模式1上的:
nullglob
可用于避免此额外行。failglob
shell选项,但未找到匹配项,则会打印错误消息并且不执行该命令。&#34; (来自上面的Bash手册)globstar
:&#34;如果设置,文件名扩展上下文中使用的模式“**”将匹配所有文件以及零个或多个目录和子目录。如果模式后跟'/',则只有目录和子目录匹配。&#34;见Bash Manual, Shopt Builtin extglob
,nocaseglob
,dotglob
&amp; shell变量GLOBIGNORE
:
文件名可以包含空格,制表符,空格,换行符,...以安全的方式处理文件名,使用find
-print0
:文件名打印所有控制字符和放大器;终止于NUL。另请参阅Gnu Findutils Manpage, Unsafe File Name Handling,safe File Name Handling,unusual characters in filenames。有关此主题的详细讨论,请参阅下面的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
始终是&#34; true&#34; &安培;代码将始终回显&#34;没有找到文件&#34;。原因是:管道的每个命令都在一个单独的子shell中执行,因此循环内部更改的变量(单独的子shell)不会更改主shell脚本中的变量。这就是为什么我建议使用流程替换作为更好的&#34;更有用,更通用的模式。
其他参考资料&amp;来源:强>
答案 4 :(得分:6)
# 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
答案 5 :(得分:5)
如果您希望稍后将输出用作以下内容,则可以将find
输出存储在数组中:
array=($(find . -name "*.txt"))
现在要在新行中打印每个元素,您可以使用for
循环迭代到数组的所有元素,也可以使用printf语句。
for i in ${array[@]};do echo $i; done
或
printf '%s\n' "${array[@]}"
您也可以使用:
for file in "`find . -name "*.txt"`"; do echo "$file"; done
这将在换行符中打印每个文件名
要仅以列表形式打印find
输出,您可以使用以下任一项:
find . -name "*.txt" -print 2>/dev/null
或
find . -name "*.txt" -print | grep -v 'Permission denied'
这将删除错误消息,并仅在新行中将文件名作为输出。
如果您希望对文件名执行某些操作,将其存储在数组中是好的,否则无需占用该空间,您可以直接打印find
的输出。
答案 6 :(得分:3)
如果您可以假设文件名不包含换行符,则可以使用以下命令将find
的输出读入Bash数组:
readarray -t x < <(find . -name '*.txt')
注意:
-t
会导致readarray
删除换行符。readarray
在管道中,它就无法工作,因此进程替换。readarray
自Bash 4开始提供。 Bash 4.4及更高版本还支持-d
参数来指定分隔符。使用空字符而不是换行符来分隔文件名也适用于文件名包含换行符的罕见情况:
readarray -d '' x < <(find . -name '*.txt' -print0)
readarray
也可以作为mapfile
使用相同的选项调用。
参考:https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
答案 7 :(得分:3)
(更新为包括@Socowi的优秀速度提升)
支持它的任何$SHELL
(dash / zsh / bash ...):
find . -name "*.txt" -exec $SHELL -c '
for i in "$@" ; do
echo "$i"
done
' {} +
完成。
原始答案(更短,但更慢):
find . -name "*.txt" -exec $SHELL -c '
echo "$0"
' {} \;
答案 8 :(得分:1)
您可以将find
返回的文件名放入如下数组中:
array=()
while IFS= read -r -d ''; do
array+=("$REPLY")
done < <(find . -name '*.txt' -print0)
现在,您可以循环访问数组以访问各个项目,并随意执行任何操作。
注意:这是白色空间安全。
答案 9 :(得分:1)
我喜欢使用首先分配给变量的find和IFS切换到新行,如下所示:
FilesFound=$(find . -name "*.txt")
IFSbkp="$IFS"
IFS=$'\n'
counter=1;
for file in $FilesFound; do
echo "${counter}: ${file}"
let counter++;
done
IFS="$IFSbkp"
以防您想在同一组DATA上重复更多操作,并且发现服务器上的速度很慢(I / 0高利用率)
答案 10 :(得分:0)
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
这将列出文件并提供有关属性的详细信息。
答案 11 :(得分:0)
根据@phk的其他答案和评论,使用fd#3:
(它仍允许在循环中使用stdin)
while IFS= read -r f <&3; do
echo "$f"
done 3< <(find . -iname "*filename*")
答案 12 :(得分:-4)
如果使用grep而不是find,那该怎么样?
ls | grep .txt$ > out.txt
现在您可以阅读此文件,文件名采用列表形式。