x=$(find . -name "*.txt")
echo $x
如果我在Bash shell中运行上面的代码段,我得到的是一个包含几个由空白分隔的文件名的字符串,而不是一个列表。
当然,我可以进一步用空白分隔它们以得到一个列表,但我相信有更好的方法来做到这一点。
那么,循环查找命令结果的最佳方法是什么呢?
x=$(find . -name "*.txt")
echo $x
如果我在Bash shell中运行上面的代码段,我得到的是一个包含几个由空白分隔的文件名的字符串,而不是一个列表。
当然,我可以进一步用空白分隔它们以得到一个列表,但我相信有更好的方法来做到这一点。
那么,循环查找命令结果的最佳方法是什么呢?
当前回答
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循环可以读取它。
其他回答
查找<path> -xdev -type f -name *.txt -exec ls -l {} \;
这将列出文件并给出有关属性的详细信息。
如果使用grep而不是find呢?
ls | grep .txt$ > out.txt
现在您可以读取这个文件,文件名以列表的形式出现。
你可以把find返回的文件名放入这样一个数组:
array=()
while IFS= read -r -d ''; do
array+=("$REPLY")
done < <(find . -name '*.txt' -print0)
现在,您可以循环遍历数组以访问单个项,并对它们做任何您想做的事情。
注意:它是空白安全的。
TL;DR:如果你只是想知道最正确的答案,你可能想知道我的个人偏好(见本文底部):
# execute `process` once for each file
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
当你可以的时候,最好是glob。空白安全,对于当前目录中的文件:
for i in *.txt; do # Whitespace-safe but not recursive.
process "$i"
done
通过启用globstar选项,你可以glob所有匹配的文件在这个目录和所有子目录:
# 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来CD到每个文件的目录,并且可以使用-ok而不是-exec(或-okdir而不是-execdir)来进行交互(在为每个文件运行命令之前提示)。
*:从技术上讲,find和xargs(默认情况下)都会在命令行中使用尽可能多的参数运行命令,次数与遍历所有文件所需的次数相同。在实践中,除非您有非常多的文件,否则这并不重要,如果您超过了长度,但需要在同一个命令行上全部使用它们,那么SOL就会找到不同的方法。
根据@phk的其他回答和评论,使用fd #3: (仍然允许在循环中使用stdin)
while IFS= read -r f <&3; do
echo "$f"
done 3< <(find . -iname "*filename*")