Bash 如何循环查找返回的文件名?

Bash 如何循环查找返回的文件名?,bash,find,Bash,Find,如果我在bashshell中运行上述代码,我得到的是一个字符串,其中包含多个文件名,用空格分隔,而不是一个列表 当然,我可以进一步用空格分隔它们,以得到一个列表,但我相信有更好的方法 那么,循环查看find命令的结果的最佳方法是什么 x=$(find . -name "*.txt") echo $x 注意:此方法和bmargulies显示的(第二个)方法可以安全地与文件/文件夹名称中的空白一起使用 为了使文件/文件夹名称中的换行符也有点奇怪,您必须使用find的-exec谓词,如下所示: fi

如果我在bashshell中运行上述代码,我得到的是一个字符串,其中包含多个文件名,用空格分隔,而不是一个列表

当然,我可以进一步用空格分隔它们,以得到一个列表,但我相信有更好的方法

那么,循环查看
find
命令的结果的最佳方法是什么

x=$(find . -name "*.txt")
echo $x
注意:此方法和bmargulies显示的(第二个)方法可以安全地与文件/文件夹名称中的空白一起使用

为了使文件/文件夹名称中的换行符也有点奇怪,您必须使用
find
-exec
谓词,如下所示:

find . -name "*.txt"|while read fname; do
  echo "$fname"
done
readarray -d '' x < <(find . -name '*.txt' -print0)
{}
是找到的项和
\的占位符
用于终止
-exec
谓词

为了完整起见,让我添加另一个变体-你必须喜欢*nix方式的多功能性:

find . -name '*.txt' -exec echo "{}" \;

这将使用
\0
字符分隔打印项目,据我所知,任何文件系统中的文件或文件夹名称都不允许使用该字符,因此应涵盖所有基础
xargs
逐个拾取它们,然后…

TL;DR:如果你只是想得到最正确的答案,你可能想知道我个人的偏好(见本文底部):

find . -name '*.txt' -print0|xargs -0 -n 1 echo
如果你有时间,请通读其余部分,了解几种不同的方法以及其中大多数方法的问题


完整答案是:

最好的方法取决于你想做什么,但这里有一些选择。只要子树中没有文件或文件夹的名称中有空格,您就可以在文件上循环:

# execute `process` once for each file
find . -name '*.txt' -exec process {} \;
稍微好一点,去掉临时变量
x

for i in $x; do # Not recommended, will break on whitespace
    process "$i"
done
当你有能力的时候去环球旅行要好得多。空白安全,适用于当前目录中的文件:

for i in $(find -name \*.txt); do # Not recommended, will break on whitespace
    process "$i"
done
通过启用
globstar
选项,您可以对该目录和所有子目录中的所有匹配文件进行全局搜索:

for i in *.txt; do # Whitespace-safe but not recursive.
    process "$i"
done
在某些情况下,例如,如果文件名已在文件中,则可能需要使用
读取

# Make sure globstar is enabled
shopt -s globstar
for i in **/*.txt; do # Whitespace-safe and recursive
    process "$i"
done
对于更复杂的搜索,您可能希望使用
find
,或者使用
-exec
选项,或者使用
-print0 | xargs-0

find . -name '*.txt' -print0 | 
    while IFS= read -r -d '' line; do 
        process "$line"
    done
find
也可以在运行命令之前通过使用
-execdir
而不是
-exec
将cd刻录到每个文件的目录中,并且可以使用
-ok
而不是
-exec>(或
-okdir
而不是
-execdir
)使其成为交互式的(在运行每个文件的命令之前进行提示)

*:从技术上讲,
find
xargs
(默认情况下)都将使用命令行上可以容纳的参数运行命令,并以浏览所有文件所需的次数运行命令。实际上,除非你有大量的文件,否则这无关紧要,如果你超过了文件的长度,但需要在同一个命令行中使用它们,那么你可以找到不同的方法。

无论你做什么,都不要使用
for
循环:

原因有三:

  • 为了使For循环均匀地启动,必须运行
    find
    直到完成
  • 如果文件名中有任何空格(包括空格、制表符或换行符),它将被视为两个单独的名称
  • 虽然现在不太可能,但您可能会溢出命令行缓冲区。想象一下,如果命令行缓冲区容纳32KB,而
    for
    循环返回40KB的文本。最后8KB将直接从
    for
    循环中删除,您永远不会知道

阅读时始终使用
结构:

# Don't do this
for file in $(find . -name "*.txt")
do
    …code using "$file"
done
循环将在执行
find
命令时执行。此外,即使返回的文件名中有空格,该命令也可以工作。而且,您不会使命令行缓冲区溢出


-print0
将使用NULL作为文件分隔符,而不是换行符,
-d$'\0'
将在读取时使用NULL作为分隔符。

如果使用grep而不是find如何

find . -name "*.txt" -print0 | while read -d $'\0' file
do
    …code using "$file"
done
现在您可以读取此文件,文件名以列表的形式出现。

find-xdev-type f-name*.txt-exec ls-l{}


这将列出文件并提供有关属性的详细信息。

如果您希望以后将输出用作以下用途,则可以将
查找
输出存储在数组中:

ls | grep .txt$ > out.txt
现在要打印新行中的每个元素,可以使用
for
循环迭代数组的所有元素,也可以使用printf语句

array=($(find . -name "*.txt"))


您还可以使用:

printf '%s\n' "${array[@]}"
这将以换行方式打印每个文件名

要仅以列表形式打印
find
输出,可以使用以下任一选项:

for file in "`find . -name "*.txt"`"; do echo "$file"; done

这将删除错误消息,并仅在新行中输出文件名


如果您希望对文件名执行某些操作,将其存储在数组中是好的,否则不需要占用该空间,您可以直接打印
find

的输出。如果您可以假设文件名不包含换行符,则可以使用以下命令将
find
的输出读取到Bash数组中:

find . -name "*.txt" -print | grep -v 'Permission denied'
readarray
也可以使用相同的选项作为
mapfile
调用


参考:

您可以将
find
返回的文件名放入如下数组:

find . -name "*.txt"|while read fname; do
  echo "$fname"
done
readarray -d '' x < <(find . -name '*.txt' -print0)
array=()
而IFS=read-r-d';做
数组+=(“$REPLY”)

完成<基于@phk的其他回答和评论,使用fd#3:
(仍允许在循环内使用标准输入法)


而IFS=read-rf文件名可以包含空格甚至控制字符。空格是bash中shell扩展的(默认)分隔符,因此不建议使用问题中的
x=$(find.-name“*.txt”)
。如果find获得一个带有空格的文件名,例如
“the file.txt”
,则如果处理
,将得到两个单独的字符串进行处理
find . -name "*.txt" -print 2>/dev/null
find . -name "*.txt" -print | grep -v 'Permission denied'
readarray -t x < <(find . -name '*.txt')
readarray -d '' x < <(find . -name '*.txt' -print0)
array=()
while IFS=  read -r -d ''; do
    array+=("$REPLY")
done < <(find . -name '*.txt' -print0)
while IFS= read -r f <&3; do
    echo "$f"

done 3< <(find . -iname "*filename*")
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
while IFS= read -r -d '' file; do
    # single filename is in $file
    echo "$file"
    # your code here
done < <(find . -name "*.txt" -print0)
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"
find . -name "*.txt" -exec $SHELL -c '
    for i in "$@" ; do
        echo "$i"
    done
' {} +
find . -name "*.txt" -exec $SHELL -c '
    echo "$0"
' {} \;
FilesFound=$(find . -name "*.txt")

IFSbkp="$IFS"
IFS=$'\n'
counter=1;
for file in $FilesFound; do
    echo "${counter}: ${file}"
    let counter++;
done
IFS="$IFSbkp"