Когда я должен использовать 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.