Когда я должен использовать Write-Error против Throw? Завершение и не прекращение ошибок

145

Глядя на скрипт 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 с последующим возвратом. Есть ли разница?

Билл Барри
источник
4
Что вы имеете в виду? Если Write_error позволяет сценарию продолжаться, очень понятно иметь оператор return после Write-Error. Ошибка была записана, и вы возвращаетесь обратно к коду, который вызвал функцию в первую очередь. Так как Throw предназначен для завершения ошибок, он автоматически завершится, так что оператор return в объявлении throw бесполезен
Gisli
1
@Gisli: Важно отметить , что returnэто не возвращение к вызывающему в processблоке (расширенный) функции; вместо этого он переходит к следующему входному объекту в конвейере. Действительно, это типичный сценарий для генерации нескончаемых ошибок: если обработка дополнительных входных объектов все еще возможна.
mklement0
1
Обратите внимание, что Throwгенерируется ошибка, определяющая сценарий , которая не совпадает с ошибкой, определяющей оператор, которая вызывается, например, с помощью Get-Item -NoSuchParameterили 1 / 0.
mklement0

Ответы:

189

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. Это не нарушит обработку, как можно было бы ожидать.

Больше информации:

Энди Арисменди
источник
Итак, Throw остановит все, Write-Error + return остановит только текущую функцию.
Билл Барри
@BillBarry Я немного обновил свой ответ объяснением return.
Энди Арисменди
Как насчет Write-Error с последующим выходом (1), чтобы гарантировать, что соответствующий код ошибки возвращен в ОС? Это когда-нибудь уместно?
Пабрамс
19

Основное различие между командлетом Write-Error и ключевым словом throw в PowerShell заключается в том, что первый просто печатает некоторый текст в стандартный поток ошибок (stderr) , тогда как последний фактически прекращает обработку выполняемой команды или функции, которая затем обрабатывается. PowerShell, отправив информацию об ошибке на консоль.

Вы можете наблюдать за их поведением в приведенных вами примерах:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

В этом примере returnбыло добавлено ключевое слово, чтобы явно остановить выполнение скрипта после отправки сообщения об ошибке на консоль. Во втором примере, с другой стороны, returnключевое слово не является необходимым, поскольку завершение неявным образом выполняется throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Энрико Кампидоглио
источник
2
если у вас есть $ ErrorActionPreference = "Stop", Write-Error также завершит процесс.
Майкл
4
Хорошая информация, но в то время как поток ошибок PowerShell является аналогом для текста на основе Stderr потока в других оболочках, как потоки всех PowerShell содержат объекты , а именно [System.Management.Automation.ErrorRecord]экземпляры, которые по умолчанию , собранное в автоматическом $Errorсборе ( $Error[0]содержит самую последнюю ошибку). Даже если вы просто использовать Write-Errorс строкой , эта строка получает завернутые в [System.Management.Automation.ErrorRecord]экземпляре.
mklement0
16

Важно : Есть 2 типа оканчивающихся ошибок , которые текущие разделы справки , к сожалению , приравнивать :

  • Заявление -terminating ошибка, как сообщают командлеты в некоторых невосстанавливаемых ситуациях и выражениямив которых / происходит ошибка PS выполнения исключения .NET; только оператор завершается, и выполнение сценария продолжается по умолчанию .

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

Для всестороннего обзора обработки ошибок PowerShell см. Эту проблему документации GitHub .

Оставшаяся часть этого поста посвящена ошибкам, не приводящим к завершению оператора и завершающим оператор .


В дополнение к существующим полезным ответам с акцентом на суть вопроса: как вы решаете , сообщать об ошибке, заканчивающейся или не заканчивающейся утверждением ?

Отчет об ошибках командлета содержит полезные рекомендации; позвольте мне попробовать прагматическое резюме :

Общая идея неразрывных ошибок состоит в том, чтобы разрешить «отказоустойчивую» обработку больших входных наборов : отказ обрабатывать подмножество входных объектов не должен (по умолчанию) прерывать - потенциально длительный - процесс в целом , что позволяет вам проверять ошибки и позже обрабатывать только неисправные объекты - как сообщается в записях ошибок, собранных в автоматической переменной $Error.

  • Сообщить о недопустимой ошибке, если ваш командлет / расширенная функция:

    • принимает МНОГОКРАТНЫЕ входные объекты через входные данные конвейера и / или массивные параметры, И
    • ошибки возникают для СПЕЦИАЛЬНЫХ объектов ввода , И
    • эти ошибки НЕ ПРЕДОТВРАЩАЮТ ОБРАБОТКУ ДАЛЬНЕЙШИХ ВХОДНЫХ ОБЪЕКТОВ В ПРИНЦИПЕ ( ситуативно , возможно, не осталось никаких входных объектов и / или предыдущие входные объекты, возможно, уже были успешно обработаны).
      • В расширенных функциях используйте $PSCmdlet.WriteError()для сообщения о нескончаемой ошибке ( Write-Errorк сожалению, она не $?должна быть установлена $Falseв области вызова - см. Эту проблему GitHub ).
      • Обработка нескончаемой ошибки: $?сообщает, сообщила ли самая последняя команда хотя бы об одной нескончаемой ошибке.
        • Таким образом, $?бытие $Falseможет означать, что любое (непустое) подмножество входных объектов не было должным образом обработано, возможно, весь набор.
        • Переменная предпочтения $ErrorActionPreferenceи / или параметр общего командлета -ErrorActionмогут изменять поведение не прекращающихся ошибок (только) с точки зрения поведения вывода ошибок и того, следует ли преобразовывать не прекращающиеся ошибки в определяющие сценарий ошибки .
  • Сообщите об ошибке ЗАКЛЮЧИТЕЛЬНОЕ ЗАЯВЛЕНИЕ во всех других случаях .

    • В частности, если в командлете / расширенной функции возникает ошибка, которая принимает только один входной или нулевой входной объект и выводит NO или единственный выходной объект или принимает только входной параметр, а заданные значения параметров предотвращают осмысленную работу.
      • В расширенных функциях вы должны использовать $PSCmdlet.ThrowTerminatingError()для того, чтобы генерировать ошибку завершения оператора.
      • Следует отметить , что, в отличие от этого Throwключевого слова генерирует сценарий ошибки -terminating , что прерывает весь скрипт (технически: текущая нить ).
      • Обработка ошибки завершения оператора: можно использовать try/catchобработчик или trapоператор (который нельзя использовать с ошибками без завершения ), но обратите внимание, что даже ошибки, определяющие операторы по умолчанию, не препятствуют выполнению остальной части сценария. Как и в случае ошибок без завершения , $?отражает, $Falseвызвало ли предыдущее утверждение ошибку завершения в операторе.

К сожалению, не все собственные командлеты ядра PowerShell играют по этим правилам :

  • Хотя маловероятно, что New-TemporaryFileпроизойдет сбой, (PSv5 +) сообщит о непрекращающейся ошибке, если произойдет сбой, несмотря на то, что он не принимает входные данные конвейера и создает только один выходной объект - это было исправлено по крайней мере в PowerShell [Core] 7.0, однако: см. Этот GitHub вопрос .

  • Resume-JobВ справке утверждается, что передача неподдерживаемого типа задания (например, задания, созданного с помощью Start-Job, который не поддерживается, потому что Resume-Jobприменяется только к заданиям рабочего процесса ) вызывает ошибку завершения, но это не так для PSv5.1.

mklement0
источник
8

Write-Errorпозволяет потребителю функции подавить сообщение об ошибке с помощью -ErrorAction SilentlyContinue(альтернативно -ea 0). Хотя throwтребуетtry{...} catch {..}

Чтобы попробовать ... поймать с Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
источник
Зачем вообще нужно вызывать Write-Error, если вы хотите подавить сообщение об ошибке?
Майкл
8

Дополнение к ответу Энди Арисменди :

Завершает ли процесс запись-ошибка или нет, зависит от $ErrorActionPreferenceнастройки.

Для нетривиальных скриптов, $ErrorActionPreference = "Stop"это рекомендуемое значение потерпеть неудачу быстро.

«Поведение PowerShell по умолчанию в отношении ошибок, которое должно продолжаться при ошибке ... очень похоже на VB6« При ошибке возобновить дальше »-ish»

http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/ )

Тем не менее, он делает Write-Errorзвонки, заканчивающиеся.

Чтобы использовать Write-Error как не завершающую команду независимо от других настроек среды, вы можете использовать общий параметр -ErrorAction со значением Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Майкл Фрейдгейм
источник
0

Если вы правильно прочитали код, значит вы правы. Следует использовать для исправления ошибок throw, и если вы имеете дело с типами .NET, полезно также следовать соглашениям об исключениях .NET.

yzorg
источник