如何在Bash中使用for循环遍历目录中的文件

3
我试图使用for循环迭代目录中的所有文件:
#!/bin/bash

myname="bandit24"

cd /var/spool/$myname
echo "Executing and deleting all scripts in /var/spool/$myname:"

for file in /var/spool/bandit24/*; #  <----- here
do
    if [ "$file" != "." -a "$file" != ".." ];
    then
        echo "Handling $file"
        owner="$(stat --format "%U" ./$file)"
        if [ "${owner}" = "bandit24" ]; then
            timeout -s 9 60 ./$file
        fi
        rm -f ./$file
    fi
done

但是结果却是我得到以下内容:

Executing and deleting all scripts in /var/spool/bandit24:
Handling /var/spool/bandit24/*
stat: cannot stat './/var/spool/bandit24/*': No such file or directory

程序并非尝试迭代 /var/spool/bandit24/ 目录中的文件,而是尝试迭代 /var/spool/bandit24/* 文件本身。但我想迭代 /var/spool/bandit24/ 目录中的文件。如何操作? 附加问题: cd /var/spool/$myname 是什么意思(第5行)?据我理解,它用于指定我们要操作的目录,对吗?

1
输出结果与您发布的脚本不一致,请保持一致。另外,如果您将 do 放在新行上,则无需在带有 for 的行末添加 ;。至于您的附加问题:是的。 - Ljm Dullaart
2
你所看到的是 /var/spool/bandit24 为空的结果。在这种情况下,/var/spool/bandit24/* 扩展为它本身。如果你希望它扩展为空,你可以在 for 循环之前启用 nullglob bash 选项:shopt -s nullglob - Renaud Pacalet
@RenaudPacalet 我说这不是脚本的输出的原因有两个:for循环不会产生stat。在for循环中的(未指定的)命令会产生它。如果你在那里放置rm -f "$file",它就不会产生错误消息。其次,stat找不到.//var.spool/bandit24/*./使得这不太可能来自这个脚本。 - Ljm Dullaart
@LjmDullaart 我同意,但我认为 .//var/ 是一个打字错误。 - Renaud Pacalet
@RodionIskhakov 除了我已经解释的问题之外,你还有另一个问题:使用 for file in /var/spool/bandit24/*,如果目录不为空,变量 file 将会取值如 /var/spool/bandit24/foobar,而不仅仅是 foobar。因此,你不能将其与 ./$file 一起使用。解决方案:当你在 /var/spool/bandit24 目录中使用 cd 命令时,只需编写 for file in * 即可。 - Renaud Pacalet
显示剩余3条评论
4个回答

3
for f in $(find . -maxdepth 1 -type f); do
    echo "current file is $f"
done

1

for循环基本上是正确的。但是,如果目录为空,则循环将被执行一次,变量file包含文本/var/spool/bandit24/*

stat消息不是来自for循环,而是来自循环中的一个命令。

正确的方法是在继续之前测试目录是否为空。您可以放置类似于以下内容的东西:

if [ $(find . -type f | wc -l) -eq 0 ] ; then
    echo "Nothing to do"
    exit 0
fi

cd之后立即执行。

关于你的脚本,还有一些其他的评论。

  • 如果你在脚本中使用了cd,那么就不需要再指定完整路径了。
  • 你的引号并不是很一致。如果你的文件名从不包含空格或奇怪的字符,这可能不是一个问题,但是我会建议使用timeout -s 9 60 "./$file"rm -f "./file"
  • /var/spool/bandit/*永远不会包含...,所以这个测试是无用的。
  • 你也可以用if [ -f "$file" ] ; then来替换这个测试。

2
或者使用 shopt -s nullglob - Léa Gris

0

我在我的Github存储库中有一个Bash脚本,它会迭代指定目录中的所有文件。您可以指定搜索深度,就像这样。

mapfile files <<< "$(find "$f" -maxdepth "$d" ! -type d)"
for file in "${files[@]}"; do
    file="$(tr -d '\n' <<< "$file")"
    # implement your logic here
done
  • $files 是一个包含文件名的数组
  • $f 包含目录
  • $d 是搜索深度,默认为1(仅限指定文件夹)
  • $file 包含文件名,格式为 $path_to_file/$filename

1
使用mapfile -d'' files < <(find "$f" -maxdepth "$d" ! -type d -print0)读取空字符分隔的结果流会更好。此外,组合命令扩展和here-string也是不必要的。使用mapfile -t可以避免file="$(tr -d '\n' <<< "$file")"。在映射空字符分隔的条目时也不需要它。(请参阅bash shell中的help mapfile - Léa Gris

0
你看到的是/var/spool/bandit24为空的结果。在这种情况下,/var/spool/bandit24/*会扩展为其本身。如果您希望它扩展为null,在for循环之前可以启用nullglob bash选项:shopt -s nullglob。
由于变量myname被分配为bandit24,cd /var/spool/$myname与cd /var/spool/bandit24相同。您应该将其重写为cd /var/spool/"$myname" || exit 1。双引号以防myname值包含空格(现在不是这种情况,但谁知道您接下来要做什么)。而|| exit 1则是为了在目录不存在且cd命令失败时中止脚本运行。这样就可以避免意外行为,比如执行和删除当前目录中的所有脚本,而不是不存在的脚本...
使用for file in /var/spool/bandit24/*,如果目录不为空,则变量file将采用像/var/spool/bandit24/foobar这样的值,而不仅仅是foobar。因此,您不能使用./$file。解决方案:由于您已经cd到/var/spool/bandit24目录中,所以只需写成for file in *即可。
您确实应该双引号引用所有对$file的引用(例如stat --format "%U" "$file"、timeout -s 9 60 ./"$file"、rm -f "$file")。如果您不这样做,会有真正的风险。

请尝试以下方法:

#!/bin/bash

myname="bandit24"

cd /var/spool/"$myname" || exit 1
echo "Executing and deleting all scripts in /var/spool/$myname:"

shopt -s nullglob
for file in *; do
    echo "Handling $file"
    owner=$(stat --format "%U" "$file")
    if [ "$owner" = "$myname" ]; then
        timeout -s 9 60 ./"$file"
    fi
    rm -f "$file"
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接