Сценарий .ps1 получил сообщение «Отсутствует закрытие '}'» в сценарии файла .bat

Мне сложно преобразовать короткий сценарий PowerShell в сценарий файла cmd.exe .bat. Сообщение об ошибке (см. Ниже) жалуется на «Отсутствует закрытие»}. Сценарий .ps1 успешно выполняется, как и ожидалось.

Я использовал символы SEMICOLON в конце операторов присваивания. Я экранировал символ ВЕРТИКАЛЬНАЯ ЛИНИЯ (вертикальная черта) с помощью КАРТЫ (^). Что мне не хватает?

Вот сценарий .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".

Запуск сценария .ps1 из сценария файла .bat также работает должным образом.

      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, я удалил символы QUOTATION MARK. Два (2) символа ВЕРТИКАЛЬНАЯ СТРОКА (вертикальная черта) экранируются, а символ CARET в начале строки -matchрегулярное выражение экранировано. Я с самого начала избегал использования символов QUOTATION MARK в скрипте .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; ^
            }; ^
        };

1 ответ

Решение
  • В и, следовательно, в пакетных файлах действует только как escape-символ в строках без кавычек ; поэтому, как правило, вам не нужно экранировать метасимволы, такие как внутри "..."строки как - если вы это сделаете, символы сохранятся .

  • Однако похоже, что использование продолжения строки (окончание _line) со строками, заключенными в двойные кавычки для каждой строки , не работает надежно (и наличие одной строки в двойных кавычках, охватывающей несколько строк, принципиально не поддерживается), поэтому решение заключается в использовании некотируемых линий, на которых вы действительно нужны , чтобы избежать | в качестве ^|, среди других персонажей.

Вот пример, в котором используются различные синтаксические конструкции 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+, pwsh.exe, а не Windows PowerShell powershell.exe, сообщаемая командная строка является реконструированной формой, которая не обязательно отражает фактическую используемую командную строку; в частности, "" последовательности превращаются в.

Примечание:

  • Каждая внутренняя строка должна иметь самый последний символ в строке.

  • Поскольку продолжение строки не включает новую строку, его необходимо использовать для явного завершения каждого оператора PowerShell (кроме последнего).

    • Примечание. Вставка пустой строки (без ее окончания) между операторами не работает: хотя технически это приводит к фактическому переходу строки при передаче в PowerShell, отсутствие общего двойного кавычки заставляет PowerShell обрабатывать такие символы новой строки так же, как пробелы , которые поэтому по-прежнему необходимо между заявлениями.

    • По этой причине однострочные комментарии ( # ...) не поддерживаются этим методом вызова, учитывая, что такие комментарии неизменно охватывают остальную часть строки , без поддержки ; чтобы покончить с ними - см. следующий пункт.

  • Чтобы добавить комментарии , форма ^<# ... #^>- т.е. (экранированные) встроенные комментарии должны использоваться - обычные однострочные комментарии ( # ....) не поддерживаются (см. предыдущий пункт).

  • Для метасимволов требуется индивидуальное экранирование, а именно:

    • & | < > ^
    • Кроме того, если вызывается из оператора :
      • = , ; ( )
  • " символы должны быть экранированы как \"так что PowerShell рассматривает их как часть выполняемых команд (в противном случае они удаляются во время синтаксического анализа командной строки).

    • Внутри метасимволы не нужны ^-эскейп, потому что cmd.exe видит такую ​​экранированную строку для PowerShell как обычную строку с двойными кавычками.

    • Однако нормализация пробелов применяется к тому, что находится внутри \"...\"; то есть, пробеги из нескольких пробелов складываются в одну пробел каждая; если это вызывает беспокойство, используйте ^"\"...\"^" (так в оригинале).

  • Для получения дополнительной информации, включая for /f пример и как справиться с экранированием ! когда setlocal enabledelayedexpansionдействует, см. этот ответ .

Другие вопросы по тегам