我想获取当前目录及其子目录中的文件列表(我想使用单行脚本):
IFS=$(echo -en "\n\b");
for FILE in $(find -type f); do echo "$FILE"; done
通常,它会按预期工作,但最近,我的文件列表:
file_.doc
file_0.doc
file_[2006_02_25].doc
file_[2016_06_16].odt
file_[2016_06_16].pdf
file_[16-6-2006].doc
file_.pdf
file_ 4-4-2006.doc
输出是:
./file_.doc
./file_0.doc
./file_0.doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_0.doc
./file_.pdf
./file_ 4-4-2006.doc
如果我将变量 IFS 更改为:
IFS=$(echo -en "\n");
那么输出将是(更正后的):
./file_.doc
./file_0.doc
./file_[2006_02_25].doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_[16-6-2006].doc
./file_.pdf
./file_ 4-4-2006.doc
我读过的'\b'
是必要的,并找到了一个解决方案使用printf
, 代替echo
。
我的问题是:
1)你能解释一下是什么让这些输出不同吗?
2)使用上面的解决方案printf
可以替代echo -en "\n\b"
?
答案1
命令替换的输出会受到分词的影响(您可以通过设置来处理IFS
),和文件名通配。其[abc]
构造是像往常一样“匹配任何字符a
, b
, c
”,并
[2006_02_25].doc
匹配0.doc
。
在 Bash/ksh/zsh 中,可以使用双星来获取目录树中的所有文件(递归地,不仅仅是当前目录)。这应该找到与您的示例相同的文件:
shopt -s globstar # in Bash
# set -o globstar # in ksh
for file in **/* ; do
[[ -f $file ]] || continue # check it's a regular file, like find -type f
...
done
当然,find
是功能强大,所以如果你有很多条件,使用它可能会更容易。如果这样做,set -f
除了修复之外,您还应该禁用文件名通配IFS
:
set -f
IFS=$'\n'
for file in $(find -type f -some -other -conditions) ; do
...
done
或者使用while read
带有进程替换的循环:
while IFS= read -r file ; do
...
done < <(find -type f -some -other -conditions)
(上面与 类似find ... | while ...
,但它绕过了管道最后一部分在子 shell 中执行的问题。)
这两个都假设文件名不包含换行符,因为它们用作find
. (并且因为$(..)
吃掉了最后的换行符。)
至少在 Bash 中,实际上有一种方法可以使文件名中的换行符也起作用。将分隔符设置read
为空字符串可以有效地使用 NUL 字节作为分隔符。所以:
while IFS= read -d '' -r file ; do
...
done < <(find -type f -some -other -conditions -print0)
尽管这开始变得令人讨厌,因为您确实需要所有这些选项才能read
使其工作而不破坏输入。
至于IFS
... 设置IFS=$(echo -en "\n");
设置IFS
为空字符串(因为命令替换会吃掉尾随换行符),导致不分裂。在这种情况下,输出似乎是正确的,因为您一次性获得了所有输出find
,而不是逐行获得。这也掩盖了文件名通配的问题,因为完整的多行字符串与任何文件名不匹配并且按原样传递。
如果您执行其他操作而不只是打印循环值,您就会看到差异。尝试添加一些分隔符:
IFS=$(echo -en "\n") # same as IFS=
for FILE in $(find -type f); do echo "<$FILE>"; done
到目前为止,IFS
除了最简单的标准 shell 之外,在任何其他 shell 中设置换行符的最简单方法是IFS=$'\n'
.
答案2
不要这样做$( find ... )
。它将调用文件名生成(通配符),并且某些文件名将被解释为与其他文件名匹配的通配符模式。例如,模式file_[2006_02_25].doc
和file_[16-6-2006].doc
匹配file_0.doc
,这就是出现此文件名而不是这两个模式的原因。
此外,您的循环将不会开始迭代,直到find
命令替换中的命令生成了其所有路径名,这在一般情况下可能会占用相当多的内存,并且实际上并不是优雅的。
相反,只需使用find
(并且不要修改IFS
):
find . type -f -print
如果您想对这些文件执行其他操作,那么您可以在以下位置执行此操作-exec
:
find . -type f -exec sh -c 'printf "Found the file %s\n" "$@"' sh {} +
如果你只想处理当前目录下的文件,你可以简单地
for name in *; do
printf 'Found the name %s\n' "$name"
done
有关的: