![为什么 for 循环不会在目录上执行](https://linux22.com/image/31351/%E4%B8%BA%E4%BB%80%E4%B9%88%20for%20%E5%BE%AA%E7%8E%AF%E4%B8%8D%E4%BC%9A%E5%9C%A8%E7%9B%AE%E5%BD%95%E4%B8%8A%E6%89%A7%E8%A1%8C.png)
在下面的脚本中,第一个为了循环按预期执行,但不是第二个。我没有收到任何错误,似乎脚本只是挂起。
HOME=/root/mydir
DIR=$HOME/var
DIRWORK=$HOME/Local
for f in $(find $DIR -type f); do
lsof -n $f | grep [a-z] > /dev/null
if [ $? != 0 ]; then
echo "hi"
fi
done
for i in $(find $DIRWORK -type d -name work); do
echo "2"
done
答案1
您的脚本以危险的方式编码。
首先,我假设您正在使用 Bash shell,因为您将其标记为“/bash”和“/for”。
在我的回答中我会引用这个伟大的重击指南,这可能是学习 Bash 的最佳来源。
1)切勿使用命令替换, 的任何一个亲切,不带引号。这里有一个主要问题:使用不带引号的扩展将输出拆分为参数。
具体来说,这$(find $DIRWORK -type d -name work)
将$(find $DIR -type f)
经历分词,因此如果find
发现一个文件名称中包含空格,即“文件名”,Bash 的分词结果将传递 2 个参数供命令for
迭代,即一个用于“文件”,一个用于“名称”。在这种情况下,您希望获得“文件:没有这样的文件或目录”和“名称:没有这样的文件或目录”,而不是如果它们确实存在的话可能会对它们造成损坏。
2)按照惯例,环境变量(PATH、EDITOR、SHELL...)和内部 shell 变量(BASH_VERSION、RANDOM...)都是完全大写的。所有其他变量名称都应小写。由于变量名称区分大小写,因此此约定可以避免意外覆盖环境变量和内部变量。
您的 $DIRWORK 目录打破了该约定,并且它也未加引号,因此如果我们 let DIRWORK='/path/to/dir1 /path/to/dir2'
,find
当 $DIRWORK 未加引号时将查看两个不同的目录。使用引号的主题在 Bash 中非常重要,因此您应该“双引号”每一个扩展,以及任何可能包含特殊字符的内容,例如“$var”、“$@”、“${array[@]}”、“$(command)”。 Bash 将“单引号”内的所有内容视为字面意思。了解 ' 和 " 和 ` 之间的区别。请参阅引号,论点您可能还想看看这个链接:http://wiki.bash-hackers.org/syntax/words
这是脚本的更安全版本,我建议您改用:
my_home="/root/mydir"
my_dir="$my_home/var"
dir_work="$my_home/Local"
while IFS= read -r -d '' f; do
# I'm guessing that you also want to ignore stderr;
# this is where the 2>&1 came from.
if lsof -n "$f" | grep '[a-z]' > /dev/null 2>&1; then
echo "hey, I'm safer now!"
fi
done < <(find "$dir_work" -type f -print0)
while IFS= read -r -d '' f; do
echo "2"
done < <(find "$dir_work" -type d -name 'work' -print0)
正如您所看到的,该IFS
变量被设置为空,从而防止read
修剪行中的前导和尾随空格。该read
命令使用空字符串 ( -d ''
) 作为分隔符,读取直到到达 \0。
find
需要进行相应的修改,因此它使用-print0
选项来用 \0 而不是新行来分隔其数据 - 令人惊讶且恶意的是,它可以是文件名的一部分。将这样的文件按 \n 分成两部分将会破坏我们的代码。
您可能想阅读有关流程替代如果你不完全理解我的脚本。
之前的答案指出应该find ... | while read name; do ...; done
用于读取find
输出也可能很糟糕。上面的循环while
在一个新的子 shell 中执行,该子 shell 具有从父 shell 复制的变量副本。然后,该副本可用于您喜欢的任何用途。当while
循环结束时,子 shell 副本被丢弃,父 shell 的原始变量没有改变。
如果您的目标是修改此while
循环内的某些变量并随后在父级中使用它们,请考虑使用上面更安全的脚本,这将防止数据丢失。
答案2
这段代码
for i in $(find $DIRWORK -type d -name work); do
echo "2"
done
将首先执行这一行
find $DIRWORK -type d -name work
等待find
执行完成,然后获取输出并将其放回for
循环中
for i in the output of find; do
echo "2"
done
只有这样 for 循环才会开始执行。
因此,如果find
需要很长时间才能完成for
循环,则必须等待很长时间才能开始。
尝试find
在交互式提示中计时命令
$ time find $DIRWORK -type d -name work
看看需要多长时间。
另请注意:不应使用for
循环来循环文件名。使用这样的while
循环:read
find $DIRWORK -type d -name work | while read name; do
echo "2"
done
读这了解更多信息。
奖励:这while
与 并行执行循环find
。这意味着循环将在打印出一行while
后立即执行一次迭代。find
它不必等待find
完成执行。