循环遍历文件夹无论如何都会进入循环

循环遍历文件夹无论如何都会进入循环

通过循环遍历文件夹时

for f in $path 

我遇到了意外的行为,即即使没有文件与通配符匹配,仍然会进入循环。我正在使用 cygwin,这是我的代码

#!/bin/sh
here=$(pwd)
release(){
    release="$1"
    targetdisk="$2"
    package="$3"
    searchPath="../$package/${package}Setup/*/*/*.msi"
    echo $searchPath
    ls $searchPath
    for f in $searchPath; do
        mode=$(echo $file | sed -e 's:.*/.*/\(.*\)/.*\.msi:\1:')
        version=$(echo $file | sed -e 's:.*/\(.*\)/.*/*.\.msi:\1:')
        PREVIOUSIFS=$IFS
        IFS=#
        targetPath="$targetdisk:\\Software and tools\\Install\\$release"
        newFile="$targetPath\\$package-$mode-$release.exe"
        if [ ! -d "$targetPath" ]; then
            mkdir -p "$targetPath"
        fi
        echo $newFile
        echo $f


        #cp $file $newFile
        IFS=$PREVIOUSIFS
    done;
}
release $1 $2 MyStuff

我故意放了 MyStuff,它不存在:

$ ./release.sh 1.0-dev G
../MyStuff/MyStuffSetup/*/*/*.msi
ls: ../MyStuff/MyStuffSetup/*/*/*.msi: No such file or directory
G:\Software and tools\Install\1.0-dev\MyStuff--1.0-dev.exe
../MyStuff/MyStuffSetup/*/*/*.msi

然而,正如您所看到的,两个回显都被执行了。为什么即使没有匹配也会发生这种情况?

答案1

与任何文件都不匹配的通配符模式将不展开。目的是允许在命令中键入包含通配符但不属于模式的参数。这在命令行上有一定的意义,它允许偶尔保存两个引号字符。在脚本中,这是非常烦人的。不幸的是,没有办法改变传统 sh 中的这种行为。您可以检查图案是否未展开。

for f in "../$package/${package}Setup"/*/*/*.msi; do
  if [ "$f" = "../$package/${package}Setup"/*/*/*.msi" ]; then
    # No match
    break
  fi
done

可以指示 Bash 将不匹配的通配符模式扩展为空列表。将您的 shebang 行更改为#!/bin/bash,您可以执行以下操作:

shopt -s nullglob
for f in "../$package/${package}Setup"/*/*/*.msi; do

您的脚本中还存在其他问题。searchPath如果 中存在空格或通配符(包括反斜杠),则使用变量的方式将会中断$package。不要将$searchPath变量替换保留为不带引号的。始终在变量替换周围使用双引号,除非您希望将它们视为以空格分隔的模式列表,但这里的情况并非如此 - $package是一个字符串(需要双引号),而明显*使该批次成为通配符模式。将模式直接放入for循环中,如上所述。

对于日志记录,不要使用这些echo命令(变量替换周围缺少双引号,因此参数将被破坏),而是使用set -x在执行时在 stderr 上显示每个命令。

在脚本中的某个时刻,您正在设置IFS=#,但您从未对 进行任何拆分#IFS如果您不打算使用它,请不要设置。

有几个问题mode=$(echo $file | sed -e 's:.*/.*/\(.*\)/.*\.msi:\1:')。如上所述,它缺少双引号"$file"。此外,为每个文件调用额外的处理速度sed很慢(在 Cygwin 上执行外部进程很慢)并且过于复杂。 shell 具有字符串处理结构;虽然它们很原始,但在这里就足够了。你的

dir="${file%/*}"
mode="${dir##*/}"
dir="${dir%/*}"
version="${dir##*/}"

答案2

该脚本有很多问题,但您所询问的行为(为什么循环执行)是这样的:

在外壳构造中

for f in <expr>; do <commands>; done

文件模式通常用于“expr”,然后将其替换为 shell 文件名通配符,这就是您所做的。但是您的表达式没有匹配任何内容,因此通配符不会将其扩展为文件路径列表。但它也不是像许多其他语言那样的零长度(空)列表,而是带有您在循环上方回显的星号的路径表达式。因此循环执行一次,并将 $f 设置为等于以 *.msi 结尾的未展开字符串。您可以在循环之前回显它的位置看到这一点。我们知道这不匹配任何内容,因为您也将它与 ls 一起使用,这就是您收到“没有这样的文件或目录”错误的原因。

(另请注意,您在循环中使用 $file,我认为您的意思是在那里使用 $f 。

相关内容