x=$(find . -name "*.txt")
echo $x

如果我在Bash shell中运行上面的代码段,我得到的是一个包含几个由空白分隔的文件名的字符串,而不是一个列表。

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

那么,循环查找命令结果的最佳方法是什么呢?


当前回答

如果你可以假设文件名不包含换行符,你可以使用以下命令将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

其他回答

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

# Don't do this
for file in $(find . -name "*.txt")
do
    …code using "$file"
done

三个原因:

为了使For循环开始,find必须运行到完成。 如果一个文件名中有任何空格(包括空格、制表符或换行符),它将被视为两个独立的名称。 虽然现在不太可能,但您可以溢出命令行缓冲区。想象一下,如果您的命令行缓冲区有32KB,而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作为分隔符。

function loop_through(){
        length_="$(find . -name '*.txt' | wc -l)"
        length_="${length_#"${length_%%[![:space:]]*}"}"
        length_="${length_%"${length_##*[![:space:]]}"}"   
        for i in {1..$length_}
        do
            x=$(find . -name '*.txt' | sort | head -$i | tail -1)
            echo $x
        done

}

为了获取循环文件列表的长度,我使用了第一个命令“wc -l”。 该命令被设置为一个变量。 然后,我需要从变量中删除尾随的空格,以便for循环可以读取它。

我认为使用这段代码(在while done之后使用管道命令):

while read fname; do
  echo "$fname"
done <<< "$(find . -name "*.txt")"

比这个答案更好,因为while循环是在子shell中执行的,如果你使用这个答案,并且在while循环之后,如果你想修改循环中的变量,就不能看到变量的变化。

你可以存储你的查找输出在数组中,如果你希望以后使用输出:

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

现在要打印new line中的每个元素,可以使用for循环迭代数组的所有元素,也可以使用printf语句。

for i in ${array[@]};do echo $i; done

or

printf '%s\n' "${array[@]}"

你还可以使用:

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

这将以换行符打印每个文件名

若要仅以列表形式打印查找输出,可以使用以下方法之一:

find . -name "*.txt" -print 2>/dev/null

or

find . -name "*.txt" -print | grep -v 'Permission denied'

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

如果您希望对文件名做一些事情,将其存储在数组中是很好的,否则不需要占用该空间,您可以直接从find输出。

我喜欢使用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"

正如@Konrad Rudolph所评论的那样,这将不适用于文件名中的“新行”。我仍然认为它很方便,因为它涵盖了需要遍历命令输出的大多数情况。