为什么 for 循环不会在目录上执行

为什么 for 循环不会在目录上执行

在下面的脚本中,第一个为了循环按预期执行,但不是第二个。我没有收到任何错误,似乎脚本只是挂起。

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完成执行。

相关内容