Когда я должен использовать Write-Error против Throw? Завершение и не прекращение ошибок
Глядя на скрипт Get-WebFile на PoshCode, http://poshcode.org/3226, я заметил странную для меня штуковину:
$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return
В чем причина этого, а не в следующем?
$URL_Format_Error = [string]"..."
Throw $URL_Format_Error
Или даже лучше:
$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Как я понимаю, вы должны использовать Write-Error для нескончаемых ошибок и Throw для завершающих ошибок, поэтому мне кажется, что вы не должны использовать Write-Error с последующим возвратом. Есть ли разница?
Ответы
Ответ 1
Write-Error
следует использовать, если вы хотите сообщить пользователю о некритической ошибке. По умолчанию все, что он делает, это печатает сообщение об ошибке красным текстом на консоли. Это не останавливает конвейер или цикл от продолжения. Throw
с другой стороны производит то, что называют завершающей ошибкой. Если вы используете команду throw, конвейер и/или токовая петля будут прерваны. Фактически все выполнение будет прекращено, если вы не используете trap
или структуру try/catch
для обработки ошибки завершения.
Следует отметить одну вещь: если вы установите $ErrorActionPreference
в "Stop"
и используете Write-Error
это приведет к завершающей ошибке.
В скрипте, с которым вы связаны, мы находим это:
if ($url.Contains("http")) {
$request = [System.Net.HttpWebRequest]::Create($url)
}
else {
$URL_Format_Error = [string]"Connection protocol not specified. Recommended action: Try again using protocol (for example 'http://" + $url + "') instead. Function aborting..."
Write-Error $URL_Format_Error
return
}
Похоже, что автор этой функции хотел остановить выполнение этой функции и отобразить сообщение об ошибке на экране, но не хотел, чтобы весь сценарий прекратил выполнение. Автор сценария мог бы использовать throw
однако это означало бы, что вам придется использовать try/catch
при вызове функции.
return
приведет к выходу из текущей области видимости, которая может быть функцией, сценарием или блоком сценария. Это лучше всего иллюстрируется кодом:
# A foreach loop.
foreach ( $i in (1..10) ) { Write-Host $i ; if ($i -eq 5) { return } }
# A for loop.
for ($i = 1; $i -le 10; $i++) { Write-Host $i ; if ($i -eq 5) { return } }
Выход для обоих:
1
2
3
4
5
Одна ошибка здесь - это использование return
с ForEach-Object
. Это не нарушит обработку, как можно было бы ожидать.
Дополнительная информация:
Ответ 2
Основное различие между командлетом Write-Error и ключевым словом throw в PowerShell заключается в том, что первый просто печатает некоторый текст в стандартный поток ошибок (stderr), а последний на самом деле завершает обработку выполняемой командой или функцией, которая затем обрабатывается PowerShell, отправив информацию об ошибке на консоль.
В приведенных вами примерах вы можете наблюдать различное поведение этих двух элементов:
$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return
В этом примере было добавлено ключевое слово return
, чтобы явно остановить выполнение script после того, как сообщение об ошибке было отправлено на консоль. Во втором примере, с другой стороны, ключевое слово return
не требуется, так как окончание неявно выполняется с помощью throw
:
$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Ответ 3
Важно. Существует два типа завершающих ошибок, которые, к сожалению, совпадают в текущих разделах справки:
завершающие операторы ошибки, о которых сообщают командлеты в определенных невосстановимых ситуациях и выражения, в которых возникает исключение .NET/ошибка времени выполнения PS; только инструкция завершается, и выполнение сценария продолжается по умолчанию.
завершающие сценарий ошибки, вызванные Throw
или эскалацией одного из других типов ошибок через переменную предпочтения-действия-ошибки/значение параметра Stop
.
Если они не перехвачены, они завершают текущий поток выполнения (т.е. не только текущий скрипт, но и всех его вызывающих, если это применимо).
Полный обзор обработки ошибок PowerShell см. в этой проблеме с документацией GitHub.
Остальная часть этого поста посвящена ошибкам, не приводящим к завершению, а завершающим оператор.
Чтобы дополнить существующие полезные ответы с акцентом на суть вопроса: Как выбрать, следует ли сообщать об ошибке завершения или прекращения оператора?
Отчет об ошибках командлета содержит полезные рекомендации; позвольте мне попробовать прагматическое резюме:
Общая идея неразрывных ошибок - разрешить "отказоустойчивую" обработку больших входных наборов: отказ обрабатывать подмножество входных объектов не должен (по умолчанию) прерывать - потенциально длительный - процесс как в целом, позволяя вам проверять ошибки и обрабатывать только неисправные объекты позже - как сообщается в записях ошибок, собранных в автоматической переменной $Error
.
Сообщите об недетерминированной ошибке, если ваш командлет/расширенная функция:
- принимает НЕСКОЛЬКО объектов ввода через входные данные конвейера и/или параметры со значениями массива, И
- ошибки возникают для СПЕЦИАЛЬНЫХ объектов ввода, И
- эти ошибки НЕ ПРЕДОТВРАЩАЮТ ОБРАБОТКУ ДАЛЬНЕЙШИХ ВХОДНЫХ ОБЪЕКТОВ В ПРИНЦИПЕ (в некоторых случаях входных объектов может не быть, и/или предыдущие входные объекты уже могут быть успешно обработаны).
- В расширенных функциях используйте
$PSCmdlet.WriteError()
, чтобы сообщить о нескончаемой ошибке (Write-Error
, к сожалению, не приводит к тому, что $?
устанавливается в $False
в области вызова - см. эту проблему GitHub).
- Обработка нескончаемой ошибки:
$?
сообщает, сообщила ли самая последняя команда хотя бы об одной нескончаемой ошибке.
- Таким образом,
$?
, будучи $False
, может означать, что любое (непустое) подмножество входных объектов не было обработано должным образом, возможно, весь набор.
- Переменная предпочтения
$ErrorActionPreference
и/или параметр общего командлета -ErrorAction
могут изменять поведение не прекращающихся ошибок (только) с точки зрения поведения вывода ошибок и того, следует ли преобразовывать не прекращающиеся ошибки в завершающие сценарий.
Сообщите об УТВЕРЖДЕНИИ ошибки во всех других случаях.
- В частности, , если возникает ошибка в командлете/расширенной функции, которая принимает только один входной или нулевой входной объект и выводит NO или единственный выходной объект или принимает только входной параметр, а заданные значения параметров предотвращают осмысленную работу.
- В расширенных функциях вы должны использовать
$PSCmdlet.ThrowTerminatingError()
, чтобы сгенерировать ошибку завершения оператора.
- Обратите внимание, что, напротив, ключевое слово
Throw
генерирует ошибку завершения сценария, которая прерывает весь сценарий (технически: текущий поток).
- Обработка ошибки завершения оператора: можно использовать обработчик
try/catch
или оператор trap
(который нельзя использовать с ошибками без завершения), но обратите внимание, что даже ошибки завершения оператора по умолчанию не мешают остальной части сценария от бега. Как и в случае ошибок без завершения, $?
отражает $False
, если предыдущий оператор вызвал ошибку завершения оператора.
К сожалению, не все собственные командлеты ядра PowerShell играют по этим правилам:
Хотя маловероятно, что New-TemporaryFile
(PSv5+) сообщит о непрекращающейся ошибке, если она не удалась, несмотря на то, что она не принимает входные данные конвейера и создает только один выходной объект - однако это, скорее всего, изменится в v6: см. эту проблему GitHub.
Справка Resume-Job
утверждает, что передача неподдерживаемого типа задания (например, задания, созданного с помощью Start-Job
, которое не поддерживается, поскольку Resume-Job
применяется только к заданиям рабочего процесса) вызывает ошибку завершения, но это не так, как в PSv5. 1.
Ответ 4
Write-Error
позволяет потребителю функции подавлять сообщение об ошибке с помощью -ErrorAction SilentlyContinue
(альтернативно -ea 0
). В то время как для throw
требуется try{...} catch {..}
Чтобы использовать try... catch с Write-Error
:
try {
SomeFunction -ErrorAction Stop
}
catch {
DoSomething
}
Ответ 5
Дополнение к Ответ Энди Арисменди:
Write-Error завершает процесс или не зависит от параметра $ErrorActionPreference
.
Для нетривиальных сценариев $ErrorActionPreference = "Stop"
представляет собой рекомендуемый параметр, который быстро завершается с ошибкой.
"Поведение по умолчанию PowerShells по отношению к ошибкам, которое продолжить по ошибке... чувствует себя очень VB6" On Error Resume Next "-ish"
(из http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/)
Однако он вызывает завершение вызовов Write-Error
.
Чтобы использовать Write-Error в качестве команды без конца, независимо от других параметров среды, вы можете использовать общий параметр -ErrorAction
со значением Continue
:
Write-Error "Error Message" -ErrorAction:Continue
Ответ 6
Если ваше чтение кода верное, вы правы. Завершающие ошибки должны использовать throw
, и если вы имеете дело с типами .NET, полезно также следовать правилам исключений .NET.