Как надежно вызвать интерфейс командной строки 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
должны были быть представлены.
- Предостережение: для долгосрочной стабильности вашего кода вы должны использовать полные имена параметров или их официальные псевдонимы. Обратите внимание, что «эластичный синтаксис» PowerShell также позволяет вам использовать префиксы имен параметров ad hoc , если такой префикс однозначно определяет целевой параметр; например,
поддерживает больше параметров, чем, например,
-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
; обратите внимание, однако, что это имеет далеко идущие последствия и что функция все еще находится в стадии бета-тестирования на момент написания этой статьи - см. этот ответ .
- В Windows 10 вы можете переключиться на UTF-8 в масштабе всей системы, при котором для кодовой страницы OEM и ANSI устанавливается значение
На 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
- При передаче кода PowerShell для выполнения через stdin в принципе работает (по умолчанию, что подразумевает
Обработка выходного потока (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\" "
В не-оболочке сценариев Призыва,
\"
можно смело использовать в обеих редакциях ; например, из WindowsRun
dialog (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
.