首页 > 解决方案 > .ps1 脚本在 .bat 文件脚本中报告为“缺少关闭 '}'”

问题描述

我很难将简短的 PowerShell 脚本转换为 cmd.exe .bat 文件脚本。错误消息(见下文)抱怨“缺少关闭 '}”。.ps1 脚本按预期成功运行。

我在赋值语句的末尾使用了分号字符。我用 CARET (^) 转义了 VERTICAL LINE(竖线)字符。我错过了什么?

这是 .bat 脚本和错误消息输出。

PS C:\src\t> Get-Content -Path .\DistributeFiles2.bat
powershell -NoLogo -NoProfile -Command ^
    "$ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project';" ^
    "$NFilesPerDirectory = 400;" ^
    "Get-ChildItem -File -Path (Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\images') -Filter '*.jpeg' ^|" ^
        "ForEach-Object {" ^
            "# Check to see if the filename starts with four (4) digits.;" ^
            "if ($_.BaseName -match '^(\d{4}).*') {" ^
                "$FolderNumber = [math]::Floor([int]$Matches[1] / $NFilesPerDirectory);" ^
                "$FolderName = 'Folder' + $FolderNumber.ToString();" ^
                "$FolderPath = Join-Path -Path $ProjectPath -ChildPath $FolderName;" ^
                "# If the destination directory does not exist, create it.;" ^
                "if (-not (Test-Path -Path $FolderPath)) { mkdir $FolderPath -WhatIf ^| Out-Null }" ^
                "# Move the file to the destination directory.;" ^
                "Move-Item -Path $_.FullName -Destination $FolderPath -WhatIf" ^
            "}" ^
        "}"

回到 cmd.exe 外壳...

C:>DistributeFiles2.bat

 9:55:41.67  C:\src\t
C:>powershell -NoLogo -NoProfile -Command     "$ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project';"     "$NFilesPerDirectory = 400;"     "Get-ChildItem -File -Path (Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\images') -Filter '*.jpeg' ^|"         "ForEach-Object {"             "# Check to see if the filename starts with four (4) digits.;"             "if ($_.BaseName -match '^(\d{4}).*') {"                 "$FolderNumber = [math]::Floor([int]$Matches[1] / $NFilesPerDirectory);"                 "$FolderName = 'Folder' + $FolderNumber.ToString();"                 "$FolderPath = Join-Path -Path $ProjectPath -ChildPath $FolderName;"                 "# If the destination directory does not exist, create it.;"                 "if (-not (Test-Path -Path $FolderPath)) { mkdir $FolderPath -WhatIf ^| Out-Null }"                 "# Move the file to the destination directory.;"                 "Move-Item -Path $_.FullName -Destination $FolderPath -WhatIf"             "}"         "}"
Missing closing '}' in statement block or type definition.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndCurlyBrace

这是按预期工作的原始 .ps1 脚本。

PS C:\src\t> Get-Content -Path .\DistributeFiles.ps1
$ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project';
$NFilesPerDirectory = 400;
Get-ChildItem -File -Path (Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\images') -Filter '*.jpeg' |
    ForEach-Object {
        # Check to see if the filename starts with four (4) digits.;
        if ($_.BaseName -match '^(\d{4}).*') {
            $FolderNumber = [math]::Floor([int]$Matches[1] / $NFilesPerDirectory);
            $FolderName = 'Folder' + $FolderNumber.ToString();
            $FolderPath = Join-Path -Path $ProjectPath -ChildPath $FolderName;
            # If the destination directory does not exist, create it.;
            if (-not (Test-Path -Path $FolderPath)) { mkdir $FolderPath -WhatIf | Out-Null }
            # Move the file to the destination directory.;
            Move-Item -Path $_.FullName -Destination $FolderPath -WhatIf
        }
    }
PS C:\src\t> .\DistributeFiles.ps1
What if: Performing the operation "Create Directory" on target "Destination: C:\Users\lit\Desktop\Project\Folder0".
What if: Performing the operation "Move File" on target "Item: C:\Users\lit\Desktop\images\0000.jpeg Destination: C:\Users\lit\Desktop\Project\Folder0".
What if: Performing the operation "Create Directory" on target "Destination: C:\Users\lit\Desktop\Project\Folder1".
What if: Performing the operation "Move File" on target "Item: C:\Users\lit\Desktop\images\0401.jpeg Destination: C:\Users\lit\Desktop\Project\Folder1".

从 .bat 文件脚本运行 .ps1 脚本也可以按预期工作。

PS C:\src\t> Get-Content -Path .\DistributeFiles.bat
@powershell -NoLogo -NoProfile -File "%~dp0%~n0.ps1"

PS C:\src\t> .\DistributeFiles.bat
What if: Performing the operation "Create Directory" on target "Destination: C:\Users\lit\Desktop\Project\Folder0".
What if: Performing the operation "Move File" on target "Item: C:\Users\lit\Desktop\images\0000.jpeg Destination: C:\Users\lit\Desktop\Project\Folder0".
What if: Performing the operation "Create Directory" on target "Destination: C:\Users\lit\Desktop\Project\Folder1".
What if: Performing the operation "Move File" on target "Item: C:\Users\lit\Desktop\images\0401.jpeg Destination: C:\Users\lit\Desktop\Project\Folder1".

更新:

接受@mklement0 的建议,我删除了引号字符。两 (2) 个 VERTICAL LINE(竖线)字符被转义,正则表达式中的行首 CARET-match被转义。我从一开始就避免在 .ps1 脚本中使用引号字符。“缺少关闭'}'”失败仍然发生。我错过了什么?

C:>powershell -NoLogo -NoProfile -Command ^
More?     $ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project'; ^
More?     $NFilesPerDirectory = 400; ^
More?     Get-ChildItem -File -Path (Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\images') -Filter '*.jpeg' ^| ^
More?         ForEach-Object { ^
More?             # Check to see if the filename starts with four (4) digits.; ^
More?             if ($_.BaseName -match '^^(\d{4}).*') { ^
More?                 $FolderNumber = [math]::Floor([int]$Matches[1] / $NFilesPerDirectory); ^
More?                 $FolderName = 'Folder' + $FolderNumber.ToString(); ^
More?                 $FolderPath = Join-Path -Path $ProjectPath -ChildPath $FolderName; ^
More?                 # If the destination directory does not exist, create it.; ^
More?                 if (-not (Test-Path -Path $FolderPath)) { mkdir $FolderPath -WhatIf ^| Out-Null }; ^
More?                 # Move the file to the destination directory.; ^
More?                 Move-Item -Path $_.FullName -Destination $FolderPath -WhatIf; ^
More?             }; ^
More?         };
Missing closing '}' in statement block or type definition.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndCurlyBrace

更新 2:

有了@mklement0 一贯的好建议,这里是工作代码。

powershell -NoLogo -NoProfile -Command ^
    $ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project'; ^
    $NFilesPerDirectory = 400; ^
    Get-ChildItem -File -Path (Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\images') -Filter '*.jpeg' ^| ^
        ForEach-Object { ^
            ^<# Check to see if the filename starts with four (4) digits.#^> ^
            if ($_.BaseName -match '^^(\d{4}).*') { ^
                $FolderNumber = [math]::Floor([int]$Matches[1] / $NFilesPerDirectory); ^
                $FolderName = 'Folder' + $FolderNumber.ToString(); ^
                $FolderPath = Join-Path -Path $ProjectPath -ChildPath $FolderName; ^
                ^<# If the destination directory does not exist, create it.#^> ^
                if (-not (Test-Path -Path $FolderPath)) { mkdir $FolderPath -WhatIf ^| Out-Null }; ^
                ^<# Move the file to the destination directory.#^> ^
                Move-Item -Path $_.FullName -Destination $FolderPath -WhatIf; ^
            }; ^
        };

标签: powershellcmd

解决方案


  • cmd.exe批处理文件中,因此也在批处理文件中,^仅充当未引用字符串中的转义字符;因此,一般情况下,您不需要转义cmd.exe元字符,例如字符串|内部,因为- 如果您这样做,这些字符将被保留"..."^|^

  • 但是,看起来使用带有双引号的每行字符串cmd.exe的行继续(行结尾 ^)不能正常工作(并且从根本上不支持单个双引号字符串跨越多行),所以解决方案是使用不带引号的行,您确实需要在其上转义|^|,以及其他字符。

下面是一个练习各种 PowerShell 语法结构的示例:

powershell -NoProfile -Command ^
  [Environment]::CommandLine; ^
  $ProjectPath = Join-Path -Path $Env:USERPROFILE -ChildPath 'Desktop\Project'; ^
  ^<# This is a comment - note the required *inline* comment syntax #^> ^
  $ProjectPath ^| ^
    ForEach-Object { \"[$ProjectPath]\" }; ^
  (42).ToString(); ^
  \"A & B\"

请注意[Environment]::CommandLine;作为第一条语句的包含,它将回显 PowerShell 所看到的命令行,这可以帮助进行故障排除。顺便说一句:当使用 PowerShell (Core) 7+ 的 CLI,pwsh.exe而不是 Windows PowerShell 的powershell.exe时,报告的命令行是一种重构形式,不能保证反映实际使用的命令行;值得注意的是,""序列转向\".

笔记:

  • 每个内部行必须具有^作为该行的最后一个字符

  • 因为续行不包括换行符,所以;必须用于显式终止每个 PowerShell 语句(最后一个除外)。

    • 注意:在语句之间插入空行(不以 结尾)不起作用:虽然它在传递给 PowerShell 时在技术上确实会产生一个实际的换行符,但缺少整体双引号使得 PowerShell 将此类换行符视为与空格相同,这因此仍然需要在语句之间。^;

    • 正是由于这个原因,此调用方法不支持单行注释 ( # ...) ,因为此类注释总是跨越行的其余部分,不支持结束它们 - 请参阅下一点。;

  • 为了包含注释,必须使用表单^<# ... #^>- 即(转义的)内联注释 -不支持普通的单行注释 ( # ....) (参见前一点)。

  • cmd.exe的元字符需要单独的^-escaping,即:

    • & | < > ^
    • 此外,如果从for /f语句调用:
      • = , ; ( )
  • "字符必须进行转义,\"以便PowerShell将它们视为要执行的命令的一部分(否则它们会在命令行解析期间被剥离)。

    • 在内部\"...\"cmd.exe元字符不需要-escaping ^,因为cmd.exe将这样的 PowerShell 转义字符串视为常规双引号字符串。

    • 但是,空白归一化应用于内部\"...\";即,多个空间的运行被每个折叠成一个空间;如果这是一个问题,请使用^"\"...\"^"(原文如此)。

  • 有关其他信息,包括for /f示例以及如何处理转义!何时setlocal enabledelayedexpansion生效,请参阅此答案


推荐阅读