x=$(find . -name "*.txt")
echo $x
如果我在Bash shell中运行上面的代码段,我得到的是一个包含几个由空白分隔的文件名的字符串,而不是一个列表。
当然,我可以进一步用空白分隔它们以得到一个列表,但我相信有更好的方法来做到这一点。
那么,循环查找命令结果的最佳方法是什么呢?
x=$(find . -name "*.txt")
echo $x
如果我在Bash shell中运行上面的代码段,我得到的是一个包含几个由空白分隔的文件名的字符串,而不是一个列表。
当然,我可以进一步用空白分隔它们以得到一个列表,但我相信有更好的方法来做到这一点。
那么,循环查找命令结果的最佳方法是什么呢?
当前回答
正如Kevin已经在上面的答案中发布的那样,最好的解决方案是使用bash glob的for循环,但由于bash glob默认情况下不是递归的,这可以通过bash递归函数来修复:
#!/bin/bash
set -x
set -eu -o pipefail
all_files=();
function get_all_the_files()
{
directory="$1";
for item in "$directory"/* "$directory"/.[^.]*;
do
if [[ -d "$item" ]];
then
get_all_the_files "$item";
else
all_files+=("$item");
fi;
done;
}
get_all_the_files "/tmp";
for file_path in "${all_files[@]}"
do
printf 'My file is "%s"\n' "$file_path";
done;
相关问题:
Bash loop through directory including hidden file Recursively list files from a given directory in Bash ls command: how can I get a recursive full-path listing, one line per file? List files recursively in Linux CLI with path relative to the current directory Recursively List all directories and files bash script, create array of all files in a directory How can I creates array that contains the names of all the files in a folder? How can I creates array that contains the names of all the files in a folder? How to get the list of files in a directory in a shell script?
其他回答
find . -name "*.txt"|while read fname; do
echo "$fname"
done
注意:此方法和bmargulies所示的(第二个)方法在文件/文件夹名称中使用空白时是安全的。
为了在文件/文件夹名称中包含换行符,你必须使用find的-exec谓词,就像这样:
find . -name '*.txt' -exec echo "{}" \;
{}是找到的项的占位符,\;用于终止-exec谓词。
为了完整起见,让我添加另一个变体-你必须喜欢*nix的多功能性:
find . -name '*.txt' -print0|xargs -0 -n 1 echo
这将使用\0字符分隔打印项,据我所知,在任何文件系统的文件或文件夹名称中都不允许使用\0字符,因此应该涵盖所有基础。Xargs一个接一个地把它们捡起来,然后……
我认为使用这段代码(在while done之后使用管道命令):
while read fname; do
echo "$fname"
done <<< "$(find . -name "*.txt")"
比这个答案更好,因为while循环是在子shell中执行的,如果你使用这个答案,并且在while循环之后,如果你想修改循环中的变量,就不能看到变量的变化。
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循环可以读取它。
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就会找到不同的方法。
(更新到包括@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"
' {} \;