Итак, я пытаюсь воссоздать аналогичную функцию, которую я бы использовал в Bash, но в Powershell:
yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "FAILED: $*"; }
На данный момент меня больше всего интересует функция try (). По сути, он позволяет мне обернуть команду этой функцией и позволить ей управлять кодом выхода. Эффект примерно такой:
try doSomething -args
Если doSomething
завершается с ненулевым значением, он выводит команду на stderr и останавливает выполнение сценария.
Я понимаю, что в Powershell есть действие при ошибке, которое можно использовать для прерывания скриптов, но похоже, что это применимо только к командлетам. Мне нужно что-то, что я могу использовать во всем на протяжении всего сценария. Я также стараюсь избегать тонны подробной логики try / catch, искажающей сценарий, отсюда и желание чего-то элегантного, например, try / yell / die. Таким образом, я могу написать обработку только в этой функции и просто использовать ее для вызова всего, что я хочу обработать.
Я нашел $MyInvocation
и подумал, что это может быть выход, но я не могу найти способ на самом деле выполнить его из функции. Например:
function run() {
$MyInvocation # ?? what do??
}
run doSomething -args
Я думаю, что смогу разобраться в остальном самостоятельно, я просто не совсем знаю, как написать эту функцию-оболочку. Любые идеи?
Итак, я сделал что-то глупое, выделил команду и сделал Invoke-Expression
на том, что осталось, и, похоже, это сработало. Это кажется супер хакерским, поэтому я все еще открыт для идей:
function attempt() {
$thisCommand = $MyInvocation.Line.Trim()
Write-Output $thisCommand
Invoke-Expression $thisCommand.Substring(8)
if ($LASTEXITCODE -ne 0) {
throw "Command failed $thisCommand"
exit 111
}
}
attempt doSomething -args
Если вы хотите, чтобы функция запускала произвольную команду и выдавала ошибку в случае сбоя этой команды, вы можете сделать что-то вроде этого:
function Test-Command {
try {
$cmd, $params = $args
$params = @($params)
$global:LastExitCode = 0
$output = & $cmd @params 2>&1
if ($global:LastExitCode -ne 0) {
throw $output
}
$output
} catch {
throw $_
}
}
Авария:
$cmd, $params = $args
принимает автоматическая переменная$args
(массив аргументов, переданных функции) и присваивает свой первый элемент переменной $cmd
, а остальные - переменной $params
.
$params = @($params)
гарантирует, что $params
содержит массив (необходимый для следующего шага), даже если переменная была пустой или содержала только одно значение.
& $cmd @params
вызывает команду, используя оператор вызова&
, а брызги - параметры. Использует ли НЕТInvoke-Expression
.
оператор перенаправления2>&1
объединяет вывод ошибок с нормальным выводом, так что оба выходных потока фиксируются в переменной $output
.
Если $cmd
является ошибкой Командлет PowerShell, возникает исключение, которое перехватывается оператором try
. Остальной код в блоке try
будет пропущен. Однако обратите внимание, что не все ошибки, выдаваемые командлетами PowerShell, автоматически завершают ошибки (см., Например, «Введение в обработку ошибок в PowerShell» в блоге Scripting Guy). Чтобы превратить непрекращающиеся ошибки в прерывающиеся, вам необходимо установить $ErrorActionPreference = 'Stop'
(и сбросить его до исходного значения после того, как вы закончите).
Если $cmd
является ошибкой внешняя команда, исключения не будут генерироваться, но автоматическая переменная $LastExitCode
обновляется кодом выхода команды. Команда, возвращающая ненулевой код выхода, вызовет условие if
и вызовет создание настраиваемого исключения с использованием выходных данных команды в качестве сообщения об исключении. Это исключение также перехватывается оператором try
. Остальной код в блоке try
снова пропускается.
$global:LastExitCode = 0
сбрасывает переменную $LastExitCode
перед каждым запуском. Это необходимо, поскольку только внешние команды возвращают код выхода, а командлеты PowerShell - нет. Поскольку $LastExitCode
сохраняет код выхода внешней команды, выполненной последним в текущем сеансе, отказ от сброса переменной может помешать обнаружению статуса командлета PowerShell, запущенного после внешней команды.
Последняя строка в блоке try
, которая повторяет захваченный вывод команды, достигается только в том случае, если команда не генерирует исключение и не возвращает ненулевой код выхода.
Любое обнаруженное исключение обрабатывается в блоке catch
, который просто передает исключение вызывающей стороне функции. Вместо того, чтобы бросать, вы также можете вывести ошибку и, конечно же, выйти.
Полагаю, я ошибаюсь. Требует ли от меня отправки параметров в виде хеш-таблицы?
О, я вижу, что это сработает, если я вызову через него функцию PowerShell. Но есть ли способ заставить это работать с другими командами?
FWIW ни $global:LastExitCode
, ни $LASTEXITCODE
, похоже, не работают в PWSH в реальной среде Linux. Вместо этого мне пришлось использовать $?
, который возвращает истину / ложь в зависимости от того, был ли последний код выхода успешным.
@Sinaesthetic В вашем вопросе не упоминается, что вы собираетесь запускать это в Linux, поэтому я тестировал код только на своем тестовом окне Windows. Пожалуйста, не упускайте подобные подробности из своих вопросов.
Я разрабатывал сценарии для Windows, поэтому все работало, пока я не развернул его на сервере сборки. Я упоминаю об этом здесь, потому что это меня тоже ошеломило. Я бы не стал предполагать, что Powershell будет вести себя иначе для чего-то столь распространенного. Я просто отмечаю это здесь. Во всяком случае, сейчас в тегах.
Итак, я заметил одну вещь: если я отправлю команду cli в функцию, определенную здесь, вывод этой команды не будет выведен на консоль: /
У меня отлично сработало (например, Test-Command ping 127.0.0.1
).
Когда я тестирую это с
Test-Command echo foo
, кажется, что команда запускается один раз для каждого символа в "foo", поэтому я получаю 'f' 'o' 'o' каждый в отдельной строке. Кажется, это верно для большинства команд. Например,Test-Command dotnet --info
тоже не работает, потому что считает каждое тире аргументом.