¿Cuándo debo usar Write-Error vs. Throw? Errores de terminación versus no terminación

145

Al mirar un script Get-WebFile en PoshCode, http://poshcode.org/3226 , noté este artilugio extraño para mí:

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

¿Cuál es la razón de esto en lugar de lo siguiente?

$URL_Format_Error = [string]"..."
Throw $URL_Format_Error

O mejor:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

Según tengo entendido, debe usar Write-Error para errores que no terminan, y Throw para errores de terminación, por lo que me parece que no debe usar Write-Error seguido de Return. ¿Hay una diferencia?

Bill Barry
fuente
44
¿Qué quieres decir? Si Write_error permite que el script continúe, es muy comprensible tener una declaración de retorno después de Write-Error. El error se ha escrito y, en primer lugar, vuelve al código que llamó a la función. Dado que Throw es para terminar errores, terminará automáticamente para que una declaración de devolución en una declaración de lanzamiento sea inútil
Gisli
1
@Gisli: Es importante tener en cuenta que returnno no regresar a la llamada en el processbloque de una función (avanzado); en su lugar, pasa al siguiente objeto de entrada en la tubería. De hecho, este es el escenario típico para generar errores sin terminación: si aún es posible procesar más objetos de entrada.
mklement0
1
Tenga en cuenta que Throwgenera un error de terminación de secuencia de comandos , que no es lo mismo que la declaración de errores de terminación activados, por ejemplo, por Get-Item -NoSuchParametero 1 / 0.
mklement0

Respuestas:

189

Write-Errordebe usarse si desea informar al usuario de un error no crítico. Por defecto, todo lo que hace es imprimir un mensaje de error en texto rojo en la consola. No detiene una tubería o un bucle de continuar. Throwpor otro lado produce lo que se llama un error de terminación. Si usa throw, la tubería y / o el bucle actual finalizarán. De hecho, toda la ejecución se terminará a menos que use una trapo una try/catchestructura para manejar el error de terminación.

Hay una cosa a la nota, si se establece $ErrorActionPreferencea"Stop" y utiliza Write-Errorse producirá un error de terminación .

En el script al que se vinculó, encontramos esto:

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
   }

Parece que el autor de esa función quería detener la ejecución de esa función y mostrar un mensaje de error en la pantalla, pero no quería que todo el script dejara de ejecutarse. El autor del script podría haber utilizado, throwsin embargo, significaría que tendría que usar un try/catchal llamar a la función.

returnsaldrá del ámbito actual que puede ser una función, secuencia de comandos o bloque de secuencia de comandos. Esto se ilustra mejor con el código:

# 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 } }

Salida para ambos:

1
2
3
4
5

Un gotcha aquí está usando returncon ForEach-Object. No interrumpirá el procesamiento como cabría esperar.

Más información:

Andy Arismendi
fuente
Ok, entonces Throw detendrá todo, Write-Error + return solo detendrá la función actual.
Bill Barry
@BillBarry Actualicé un poco mi respuesta con una explicación de return.
Andy Arismendi
¿Qué pasa con Write-Error seguido de exit (1) para asegurar que el código de error apropiado se devuelva al sistema operativo? ¿Es eso apropiado alguna vez?
pabrams
19

La principal diferencia entre el cmdlet Write-Error y la palabra clave throw en PowerShell es que el primero simplemente imprime algo de texto en el flujo de error estándar (stderr) , mientras que el segundo termina el procesamiento del comando o función en ejecución, que luego se maneja por PowerShell enviando información sobre el error a la consola.

Puede observar el comportamiento diferente de los dos en los ejemplos que proporcionó:

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

En este ejemplo, la returnpalabra clave se ha agregado para detener explícitamente la ejecución de la secuencia de comandos después de enviar el mensaje de error a la consola. En el segundo ejemplo, por otro lado, la returnpalabra clave no es necesaria ya que la terminación se realiza implícitamente por throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Enrico Campidoglio
fuente
2
si tiene $ ErrorActionPreference = "Stop", Write-Error también terminará el proceso.
Michael Freidgeim
44
Buena información, pero si bien el flujo de error de PowerShell es análogo al flujo stderr basado en texto en otros shells, como todos los flujos de PowerShell, contiene objetos , a saber [System.Management.Automation.ErrorRecord], instancias, que se recopilan de forma predeterminada en la $Errorcolección automática ( $Error[0]contiene el error más reciente). Incluso si solo lo usa Write-Errorcon una cadena , esa cadena se envuelve en una [System.Management.Automation.ErrorRecord]instancia.
mklement0
16

Importante : Hay 2 tipos de errores de terminación , que desafortunadamente los temas de ayuda actuales combinan :

  • errores de terminación de instrucciones , según lo informado por cmdlets en ciertas situaciones no recuperables y por expresiones en las que se produce una excepción .NET / un error de tiempo de ejecución de PS; solo la declaración finaliza y la ejecución del script continúa de manera predeterminada .

  • guión -terminating errores (más exactamente: espacio de ejecución de terminación ), ya sea como provocadas porThrowo por la escalada de uno de los otros tipos de error a través error-acción valor de preferencia variable / parámetroStop.
    A menos que sean capturados, terminan el espacio de ejecución actual (hilo); es decir, terminan no solo el script actual, sino también todos sus llamantes, si corresponde).

Para obtener una descripción completa del manejo de errores de PowerShell, consulte este problema de documentación de GitHub .

El resto de esta publicación se centra en los errores que no terminan frente a los que terminan las declaraciones .


Para complementar las respuestas útiles existentes con un enfoque en el núcleo de la pregunta: ¿Cómo elige si desea informar un error de terminación o no terminación de la declaración ?

El Informe de errores de cmdlet contiene pautas útiles; déjame intentar un resumen pragmático :

La idea general detrás de los errores que no terminan es permitir el procesamiento "tolerante a fallas" de grandes conjuntos de entrada : la falla al procesar un subconjunto de los objetos de entrada no debe (por defecto) abortar el proceso en su conjunto , potencialmente de larga duración , lo que le permite inspeccionar los errores y reprocesar solo los objetos fallidos más adelante , como se informa a través de los registros de errores recopilados en la variable automática $Error.

  • Informe un error NO TERMINATORIO , si su cmdlet / función avanzada:

    • acepta MÚLTIPLES objetos de entrada , a través de la entrada de canalización y / o parámetros con valores de matriz, Y
    • se producen errores para objetos de entrada ESPECÍFICOS , Y
    • estos errores NO PREVIENEN EL PROCESAMIENTO DE OTROS objetos de entrada EN PRINCIPIO ( situacionalmente , puede que no queden objetos de entrada y / o que los objetos de entrada anteriores ya hayan sido procesados ​​con éxito).
      • En las funciones avanzadas, use $PSCmdlet.WriteError()para informar un error que no termina ( Write-Errordesafortunadamente, no causa $?que se establezca $Falseen el alcance de la persona que llama ; consulte este problema de GitHub ).
      • Manejo de un error sin terminación: $?le indica si el comando más reciente informó al menos un error sin terminación.
        • Por lo tanto, $?ser $Falsepuede significar que cualquier subconjunto (no vacío) de objetos de entrada no se procesó correctamente, posiblemente todo el conjunto.
        • La variable de preferencia $ErrorActionPreferencey / o el parámetro de cmdlet común -ErrorActionpueden modificar el comportamiento de los errores sin terminación (solo) en términos de comportamiento de salida de error y si los errores sin terminación deben escalarse a los que terminan la secuencia de comandos .
  • Informe un error de TERMINACIÓN DE DECLARACIÓN en todos los demás casos .

    • Notablemente, si se produce un error en un cmdlet / función avanzada que solo acepta un objeto de entrada SINGLE o NO y emite NO o un objeto de salida SINGLE o toma solo la entrada de parámetros y los valores de parámetros dados impiden una operación significativa.
      • En funciones avanzadas, debe usar $PSCmdlet.ThrowTerminatingError()para generar un error de finalización de sentencia.
      • Tenga en cuenta que, por el contrario, la Throwpalabra clave genera un error de terminación de script que aborta todo el script (técnicamente: el hilo actual ).
      • Manejo de un error de terminación de declaración: Se puede usar un try/catchcontrolador o una trapdeclaración (que no se puede usar con errores que no terminan ), pero tenga en cuenta que incluso los errores de terminación de declaración por defecto no impiden que se ejecute el resto del script. Al igual que con los errores que no terminan , $?refleja $Falsesi la declaración anterior activó un error de terminación de la declaración.

Lamentablemente, no todos los cmdlets principales de PowerShell cumplen con estas reglas :

  • Si bien es poco probable que falle, New-TemporaryFile(PSv5 +) informaría un error que no termina si falla, a pesar de no aceptar la entrada de la tubería y solo producir un objeto de salida; sin embargo, esto se ha corregido al menos desde PowerShell [Core] 7.0: vea este GitHub cuestión .

  • Resume-JobLa ayuda afirma que pasar un tipo de trabajo no admitido (como un trabajo creado con Start-Job, que no es compatible, porque Resume-Jobsolo se aplica a los trabajos de flujo de trabajo) provoca un error de finalización, pero eso no es cierto a partir de PSv5.1.

mklement0
fuente
8

Write-Errorpermite al consumidor de la función suprimir el mensaje de error con -ErrorAction SilentlyContinue(alternativamente -ea 0). Si bien throwrequiere untry{...} catch {..}

Para usar un intento ... captura con Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
fuente
¿Por qué necesita llamar a Error de escritura si desea suprimir el mensaje de error?
Michael Freidgeim
8

Además de la respuesta de Andy Arismendi :

Si Write-Error finaliza el proceso o no depende de la $ErrorActionPreferenceconfiguración.

Para scripts no triviales, $ErrorActionPreference = "Stop"es una configuración recomendada para fallar rápidamente.

"El comportamiento predeterminado de PowerShell con respecto a los errores, que consiste en continuar en caso de error ... se siente muy VB6" En error Reanudar siguiente "-ish"

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

Sin embargo, hace que las Write-Errorllamadas terminen.

Para usar Write-Error como un comando que no termina, independientemente de otras configuraciones del entorno, puede usar un parámetro común -ErrorAction con valor Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Michael Freidgeim
fuente
0

Si su lectura del código es correcta, entonces está en lo correcto. Deben usarse los errores de terminación throw, y si se trata de tipos .NET, también es útil seguir las convenciones de excepción .NET.

yzorg
fuente