Как надежно вызвать интерфейс командной строки PowerShell в отношении кодировки символов, потоков ввода и вывода, цитирования и экранирования?

Этот вопрос, на который дан самостоятельный ответ, призван дать систематический обзор интерфейса командной строки PowerShell как для Windows PowerShell ( powershell.exe) и PowerShell (Core) v6+ ( pwsh.exe в Windows, pwsh в Unix).

Хотя официальные разделы справки существуют (см. Ссылки в ответе), они не дают полной картины и не имеют систематической обработки (на момент написания этой статьи).

Среди прочего, будут даны ответы на следующие вопросы:

  • Чем отличаются интерфейсы командной строки для конкретных версий?

  • Как передать код PowerShell для выполнения в интерфейсы командной строки? Как -Command ( -c) а также -File ( -f) отличаются?

  • Как аргументы, передаваемые этим параметрам, нужно заключать в кавычки и экранировать?

  • Какие проблемы с кодировкой символов возникают?

  • Как интерфейсы командной строки PowerShell обрабатывают ввод stdin, и какой stdout / stderr они производят и в каком формате?

1 ответ

Основы PowerShell CLI:

  • PowerShell издания: The CLI завещательного, в комплекте-с Windows , Windows PowerShell издание , в то время как кроссплатформенная версия PowerShell (Core) 7+, устанавливаемая по требованию, является (просто на Unix-подобных платформах).

  • Интерактивное использование:

    • По умолчанию, если код для выполнения не указан (через () или (см. Ниже), вводится интерактивный сеанс. Однако, в отличие от POSIX-совместимой оболочки, такой как, вы можете использовать для входа в интерактивный сеанс после выполнения кода . Это особенно удобно для устранения неполадок в командной строке, когда интерфейс командной строки вызывается без уже существующего окна консоли.

    • Использовать -NoLogoдля подавления текста запуска, который отображается при входе в интерактивный сеанс (не требуется, если код для выполнения передан). В выпуске GitHub № 15644 предлагается по умолчанию не показывать этот стартовый текст .

    • Чтобы отказаться от уведомлений о телеметрии / обновлении, определите следующие переменные среды перед входом в интерактивный сеанс: POWERSHELL_TELEMETRY_OPTOUT=1 / POWERSHELL_UPDATECHECK=Off

  • Параметры и значения по умолчанию:

    • Все имена параметров не чувствительны к регистру (как обычно в PowerShell); у большинства параметров есть короткие псевдонимы, например -h а также -? для -Help, который показывает справку командной строки, которая с (но не) также перечисляет эти короткие псевдонимы.

      • Предостережение: для долгосрочной стабильности вашего кода вы должны использовать полные имена параметров или их официальные псевдонимы. Обратите внимание, что «эластичный синтаксис» PowerShell также позволяет вам использовать префиксы имен параметров ad hoc , если такой префикс однозначно определяет целевой параметр; например, -ver однозначно нацелены -version в настоящее время , но - по крайней мере, гипотетически - такой вызов может прерваться в будущем, если новый параметр, имя которого также начинается с ver должны были быть представлены.
    • поддерживает больше параметров, чем, например, -WorkingDirectory ( -wd).

    • Есть два (взаимоисключающих) способа передать код для выполнения, и в этом случае процесс PowerShell завершается автоматически по завершении выполнения; проходить -NonInteractive чтобы предотвратить использование интерактивных команд в коде или оставить сеанс открытым после выполнения:

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

      • () предназначен для вызова файлов сценария () с передаваемыми аргументами, которые обрабатываются как дословные значения.

      • Этот параметр должен стоять последним в командной строке, потому что все последующие аргументы интерпретируются как часть передаваемой команды / вызова файла сценария.

      • См. чтобы узнать, когда использовать vs., и нижний раздел, чтобы узнать о цитатах / экранировании.

      • Это целесообразно использовать () или () в явном виде ; потому что две редакции имеют разные значения по умолчанию:

        • по умолчанию ()
        • по умолчанию используется (), изменение, которое было необходимо для поддержки строк shebang на Unix-подобных платформах.
      • К сожалению, даже с () или () профили (файлы инициализации) загружаются по умолчанию (в отличие от POSIX-совместимых оболочек, таких как, которые делают это только в интерактивных оболочках).

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

    • Примечание. Интерфейсы командной строки PowerShell обрабатывают только текст [1], как на входе, так и на выходе, и никогда не обрабатывают необработанные байтовые данные; то, что выводит интерфейс командной строки по умолчанию, представляет собой тот же текст, который вы видите в сеансе PowerShell, что для сложных объектов (объектов со свойствами) означает удобное для человека форматирование, не предназначенное для программной обработки, поэтому для вывода сложных объектов лучше выводить их в структурированный текстовый формат, например JSON.

      • Обратите внимание, что пока вы можете использовать -OutputFormat xml ( -of xml) для получения вывода CLIXML, который использует XML для сериализации объектов, этот конкретный формат мало используется за пределами PowerShell; то же самое для приема ввода CLIXML через stdin ( -InputFormat xml / -if xml).
    • В Windows интерфейсы командной строки PowerShell учитывают кодовую страницу консоли, как это отражено в выходных данных из chcp а внутри PowerShell в [Console]::InputEncoding. По умолчанию в качестве кодовой страницы консоли используется активная кодовая страница OEM системы .

      • Предостережение: кодовые страницы OEM, такие как 437в американско-английских системах используются фиксированные однобайтовые кодировки символов, всего до 256 символов. Чтобы получить полную поддержку Unicode, вы должны переключиться на кодовую страницу перед вызовом PowerShell CLI (из, вызов chcp 65001); хотя это работает в обеих версиях PowerShell, к сожалению, в этом случае консоль переключается на растровый шрифт, что приводит к неправильному отображению многих символов Unicode ; однако на фактические данные это не повлияет.

        • В Windows 10 вы можете переключиться на UTF-8 в масштабе всей системы, при котором для кодовой страницы OEM и ANSI устанавливается значение 65001; обратите внимание, однако, что это имеет далеко идущие последствия и что функция все еще находится в стадии бета-тестирования на момент написания этой статьи - см. этот ответ .
    • На Unix- подобных платформах (), UTF-8, это всегда используется (даже если активный языковой (по данным locale) не основан на UTF-8, но в наши дни это очень редко).

  • Обработка входного потока (stdin) (полученного черезstdin, либо переданного по конвейеру в вызов CLI, либо предоставленного через перенаправление ввода <):

    • Чтобы обработать ввод stdin как данные:

      • Явное использование автоматического $inputпеременная обязательна.

      • Это, в свою очередь, означает, что для передачи ввода stdin в файл сценария ( .ps1), () вместо () должны использоваться. Обратите внимание, что при этом в сценарий передаются любые аргументы (обозначенные символом ... ниже) при условии интерпретации PowerShell (тогда как с они будут использоваться дословно):
        -c "$Input | ./script.ps1 ..."

    • Для обработки ввода stdin как кода ( pwsh только, вроде бы взломан):

      • При передаче кода PowerShell для выполнения через stdin в принципе работает (по умолчанию, что подразумевает -File -, а также с -Command -), он проявляет нежелательное псевдо-интерактивное поведение и предотвращает передачу аргументов: см. выпуск GitHub #3223; например:
        echo "Get-Date; 'hello'" | pwsh -nologo -nop
  • Обработка выходного потока (stdout, stderr):

    • (Если вы не используете блок сценария (), который работает только изнутри PowerShell, см. Ниже), все 6 выходных потоков PowerShell отправляются на стандартный вывод , включая ошибки (!) (Последние обычно отправляются на стандартный вывод).

      • Однако, когда вы применяете перенаправление - external - stderr, вы можете выборочно подавить вывод потока ошибок ( 2>NUL из , 2>/dev/null в Unix) или отправить в файл ( 2>errs.txt).

      • См. Дополнительную информацию в нижнем разделе этого ответа .


Цитирование и экранирование аргументов () и ():

При вызове из PowerShell (требуется редко):

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

  • Если вам все еще нужно, самый надежный подход - использовать блок скрипта ( { ... }), что позволяет избежать проблем с цитированием, поскольку вы, как обычно, можете использовать собственный синтаксис PowerShell. Обратите внимание, что использование блоков сценария работает только изнутри PowerShell, и что вы не можете ссылаться на переменные вызывающего объекта в блоке сценария; однако вы можете использовать -args параметр для передачи аргументов (на основе переменных вызывающего) в блок скрипта, например, pwsh -c { "args passed: $args" } -args foo, $PID; использование блоков сценария дает дополнительные преимущества в отношении выходных потоков и поддержки типов данных, отличных от строк; см. этот ответ .

            # From PowerShell ONLY
    PS> pwsh -nop -c { "Caller PID: $($args[0]); Callee PID: $PID" } -args $PID
    

При вызове извне PowerShell (типичный случай):

Примечание:

  • () аргументы должны передаваться как отдельные аргументы : путь к файлу сценария, за которым следуют аргументы для передачи сценарию, если таковые имеются. Как путь к файлу сценария, так и аргументы передачи используются PowerShell дословно после удаления (без экранирования) двойных кавычек в Window[2].

  • () аргументы могут быть переданы как несколько аргументов, но в конце PowerShell просто объединяет их вместе с пробелами после удаления (без экранирования) двойных кавычек в Windows, прежде чем интерпретировать полученную строку как код PowerShell (как если бы вы отправили ее в Сеанс PowerShell).

    • Для надежности и концептуальной ясности лучше всего передавать команду (ы) в качестве одного аргумента функции (), для чего в Windows требуется строка, заключенная в двойные кавычки () (хотя в целом корпус не является строго необходимым для надежности в отсутствии среды вызова оболочки, такие как планировщик задач и некоторые среды CI / CD и управления конфигурацией, т. е. в тех случаях, когда сначала не обрабатывается командная строка).
  • Опять же, см. Этот ответ,Этот ответ , чтобы узнать, когда использовать -File ( -f) по сравнению с тем, когда использовать ().

  • Чтобы протестировать командную строку, вызовите ее из окна консоли или, чтобы имитировать вызов без оболочки, используйте WinKey-R(диалоговое окно) и используйте -NoExit в качестве первого параметра, чтобы получившееся окно консоли оставалось открытым.

    • Не выполняйте тестирование изнутри PowerShell, поскольку собственные правила синтаксического анализа PowerShell приведут к различной интерпретации вызова, особенно в отношении распознавания (одинарные кавычки) и потенциального предварительного расширения токенов с префиксом.

В Unix, нет особых соображений не применяются (включая Unix-на-Windows среды , такие как WSL и Git Bash):

  • Вам нужно только удовлетворить синтаксические требования вызывающей оболочки. Обычно программный вызов PowerShell CLI использует POSIX-совместимую системную оболочку по умолчанию в Unix, /bin/sh), что означает, что вложенные строки внутри строк должны быть экранированы как, и $символы, которые следует передать в PowerShell как \$; то же самое относится к интерактивным вызовам из POSIX-совместимых оболочек, таких как bash; например:

            # From Bash: $$ is interpreted by Bash, (escaped) $PID by PowerShell.
    $ pwsh -nop -c " Write-Output \"Caller PID: $$; PowerShell PID: \$PID \" "
    
    # Use single-quoting if the command string need not include values from the caller:
    $ pwsh -nop -c ' Write-Output "PowerShell PID: $PID" '
    

В Windows все сложнее:

  • '...'(одинарные кавычки) можно использовать только с () и никогда не имеет синтаксической функции в командной строке PowerShell CLI ; то есть одинарные кавычки всегда сохраняются и интерпретируются как дословные строковые литералы, когда аргумент (ы), анализируемый из командной строки, позже интерпретируется как код PowerShell; см. этот ответ для получения дополнительной информации.

  • (двойные кавычки) делает имеют синтаксическую функцию командной строки, и незаменяемые двойные кавычки раздели , которые в случае -Command ( -c) означает, что они не рассматриваются как часть кода, выполняемого PowerShell Ultimate. символы, которые вы хотите сохранить, должны быть экранированы - даже если вы передаете команду как отдельные аргументы, а не как часть одной строки.

    • powershell.exe требует экранирования как [3] (sic) - хотя внутри PowerShell это `(обратная кавычка), которая действует как escape-символ; однако это наиболее широко распространенная конвенция для побега "символы. в командной строке Windows .

      • К сожалению, это может привести к прерыванию вызовов, если символы между двумя экземплярами содержат метасимволы, такие как и |; надежный - но громоздкий и непонятный - выбор ; будет , как правило , работают, однако.

                    :: powershell.exe: from cmd.exe, use "^"" for full robustness (\" often, but not always works)
        powershell.exe -nop -c " Write-Output "^""Rock  &  Roll"^"" "
        
        :: With double nesting (note the ` (backticks) needed for PowerShell's syntax).
        powershell.exe -nop -c " Write-Output "^""The king of `"^""Rock  &  Roll`"^""."^"" "
        
        :: \" is OK here, because there's no & or similar char. involved.
        powershell.exe -nop -c " Write-Output \"Rock  and  Roll\" "
        
    • pwsh.exe принимает или же .

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

                    :: pwsh.exe: from cmd.exe, use "" for full robustness
        pwsh.exe -nop -c " Write-Output ""Rock  &  Roll"" "
        
        :: With double nesting (note the ` (backticks)).
        pwsh.exe -nop -c " Write-Output ""The king of `""Rock  &  Roll`""."" "
        
        :: \" is OK here, because there's no & or similar char. involved.
        pwsh.exe -nop -c " Write-Output \"Rock  and  Roll\" "
        
    • В не-оболочке сценариев Призыва, \"можно смело использовать в обеих редакциях ; например, из Windows Rundialog (WinKey-R); обратите внимание, что первая команда будет прервана из ( & будет интерпретироваться как разделитель операторов, и он попытается выполнить программу с именем Rollпри выходе из сеанса PowerShell; попробуйте без -noexit чтобы сразу увидеть проблему):

                  pwsh.exe -noexit -nop -c " Write-Output \"Rock  &  Roll\" "
      
        pwsh.exe -noexit -nop -c " Write-Output \"The king of `\"Rock  &  Roll`\".\" "
      

Смотрите также:

  • Головные боли с цитированием также возникают в обратном сценарии: вызов внешних программ из сеанса PowerShell: см. Этот ответ .

  • При звонке из %...%-закрытые токены, такие как %USERNAME% интерпретируются как ссылки на переменные (среды), как при использовании без кавычек, так и внутри "..."струны. Хотя это обычно желательно, иногда этого необходимо предотвратить, и, к сожалению, решение зависит от того, вызывается ли команда в интерактивном режиме или из командного файла ( .cmd, .bat): см. этот ответ .


[1] Это также относится к внутрисессионному взаимодействию PowerShell с внешними программами.

[2] В Unix, где не существует командных строк уровня процесса, PowerShell всегда получает только массив дословных аргументов, которые являются результатом синтаксического анализа командной строки вызывающей оболочкой.

[3] Использование ""это половина сломан; пытаться powershell.exe -nop -c "Write-Output 'Nat ""King"" Cole'" из cmd.exe.

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