Generar error en un script Bash

103

Quiero generar un error en un script Bash con el mensaje "¡¡¡Casos de prueba fallaron !!!". ¿Cómo hacer esto en Bash?

Por ejemplo:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
Naveen Kumar
fuente
1
¿Qué quieres que suceda con este error? ¿Cómo se llama tu guión? ¿Es solo un guión o muchos guiones? ¿Cuáles serán los usos de su script?
Etan Reisner
solo un guion. Lo llamé usando una terminal de ubuntu como ./script/test.sh
Naveen Kumar
unix.stackexchange.com
Seth McClaine
¿Sin amor por echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
infijo

Respuestas:

121

Esto depende de dónde desee que se almacene el mensaje de error.

Puede hacer lo siguiente:

echo "Error!" > logfile.log
exit 125

O lo siguiente:

echo "Error!" 1>&2
exit 64

Cuando genera una excepción, detiene la ejecución del programa.

También puede usar algo como exit xxxdónde xxxestá el código de error que quizás desee volver al sistema operativo (de 0 a 255). Aquí 125y 64solo hay códigos aleatorios con los que puede salir. Cuando necesite indicar al sistema operativo que el programa se detuvo de manera anormal (por ejemplo, se produjo un error), debe pasar un código de salida distinto de cero a exit.

Como señaló @chepner , puede hacerlo exit 1, lo que significará un error no especificado .

ForceBru
fuente
12
o puede enviarlo a stderr, donde se supone que deben ir los errores.
¿cómo enviarlo a stderr?
Naveen Kumar
2
@ user3078630, acabo de editar mi respuesta. 1>&2hará el truco
ForceBru
Si se trata de un error, también debe salir con un estado de salida distinto de cero. exitpor sí mismo usa el estado de salida del comando completado más recientemente, que puede ser 0.
chepner
3
A menos que tenga un significado específico en mente, solo debe usar exit 1, que por convención significa un error no especificado.
chepner
36

Manejo básico de errores

Si su corredor de casos de prueba devuelve un código distinto de cero para las pruebas fallidas, simplemente puede escribir:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

O incluso más corto:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

O el más corto:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Para salir con el código de salida de test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Manejo avanzado de errores

Si desea adoptar un enfoque más completo, puede tener un controlador de errores:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

luego invocalo después de ejecutar tu caso de prueba:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

o

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Las ventajas de tener un controlador de errores como exit_if_errorson:

  • podemos estandarizar toda la lógica de manejo de errores, como el registro , la impresión de un seguimiento de la pila , la notificación, la limpieza, etc., en un solo lugar
  • Al hacer que el controlador de errores obtenga el código de error como argumento, podemos evitar que la persona que llama el desorden de ifbloques que prueban los códigos de salida en busca de errores
  • si tenemos un manejador de señales (usando trampa ), podemos invocar el manejador de errores desde allí

Biblioteca de registro y manejo de errores

Aquí hay una implementación completa del manejo y registro de errores:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Artículos Relacionados

codeforester
fuente
9

Hay un par de formas más con las que puede abordar este problema. Suponiendo que uno de sus requisitos es ejecutar un script / función de shell que contenga algunos comandos de shell y verificar si el script se ejecutó correctamente y arrojar errores en caso de fallas.

Los comandos del shell generalmente se basan en los códigos de salida devueltos para informar al shell si tuvo éxito o falló debido a algunos eventos inesperados.

Entonces, lo que quiere hacer recae en estas dos categorías

  • salir en caso de error
  • salida y limpieza en caso de error

Dependiendo de cuál desee hacer, hay opciones de shell disponibles para usar. Para el primer caso, la carcasa ofrece una opción con set -ey para la segunda se podría hacer un trapsobreEXIT

¿Debo usar exiten mi script / función?

El uso exitgeneralmente mejora la legibilidad En ciertas rutinas, una vez que sepa la respuesta, querrá salir a la rutina de llamada inmediatamente. Si la rutina se define de tal manera que no requiere ninguna limpieza adicional una vez que detecta un error, no salir inmediatamente significa que debe escribir más código.

Por lo tanto, en los casos en que necesite realizar acciones de limpieza en el script para limpiar la terminación del script, se prefiere no usar exit.

¿Debo usar set -epara error al salir?

¡No!

set -efue un intento de agregar "detección automática de errores" al shell. Su objetivo era hacer que el shell abortara cada vez que ocurriera un error, pero conlleva muchos peligros potenciales, por ejemplo,

  • Los comandos que forman parte de una prueba if son inmunes. En el ejemplo, si espera que se rompa en la testverificación en el directorio no existente, no lo haría, pasa a la condición else

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
  • Los comandos de una canalización que no sea la anterior son inmunes. En el siguiente ejemplo, porque el código de salida del comando ejecutado más recientemente (más a la derecha) se considera ( cat) y fue exitoso. Esto podría evitarse estableciendo la set -o pipefailopción, pero sigue siendo una advertencia.

    set -e
    somecommand that fails | cat -
    echo survived 

Recomendado para su uso: trapa la salida

El veredicto es si desea poder manejar un error en lugar de salir a ciegas, en lugar de usar set -e, use a trapen la ERRpseudo señal.

La ERRtrampa no es ejecutar código cuando el propio shell sale con un código de error distinto de cero, sino cuando cualquier comando ejecutado por ese shell que no es parte de una condición (como si cmd, o cmd ||) sale con un estado de salida distinto de cero. .

La práctica general es que definimos un controlador de trampas para proporcionar información de depuración adicional sobre qué línea y qué causa la salida. Recuerde que el código de salida del último comando que causó la ERRseñal aún estaría disponible en este punto.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

y usamos este controlador como se muestra a continuación en la parte superior del script que está fallando

trap cleanup ERR

Poniendo esto junto en un script simple que contenía falseen la línea 15, la información que obtendría como

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

El traptambién proporciona opciones independientemente del error para ejecutar la limpieza al finalizar el shell (por ejemplo, el script de shell sale), en la señal EXIT. También puede atrapar varias señales al mismo tiempo. La lista de señales admitidas para capturar se puede encontrar en trap.1p - página de manual de Linux

Otra cosa a tener en cuenta sería comprender que ninguno de los métodos proporcionados funciona si se trata de sub-shells involucrados, en cuyo caso, es posible que deba agregar su propio manejo de errores.

  • En un sub-shell con set -eno funcionaría. El falseestá restringido al sub-shell y nunca se propaga al shell padre. Para hacer el manejo de errores aquí, agregue su propia lógica para hacer(false) || false

    set -e
    (false)
    echo survived
  • Lo mismo ocurre con traptambién. La lógica siguiente no funcionaría por las razones mencionadas anteriormente.

    trap 'echo error' ERR
    (false)
Inian
fuente
5

Aquí hay una trampa simple que imprime el último argumento de lo que falló en STDERR, informa la línea en la que falló y sale del script con el número de línea como código de salida. Tenga en cuenta que estas no siempre son buenas ideas, pero esto demuestra alguna aplicación creativa sobre la que podría desarrollar.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Lo puse en un script con un bucle para probarlo. Solo busco un acierto en algunos números aleatorios; puede utilizar pruebas reales. Si necesito fianza, llamo a false (que desencadena la trampa) con el mensaje que quiero lanzar.

Para una funcionalidad elaborada, haga que la trampa llame a una función de procesamiento. Siempre puede usar una declaración de caso en su arg ($ _) si necesita hacer más limpieza, etc. Asigne a una var para un poco de azúcar sintáctico -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Salida de muestra:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Obviamente, podrías

runTest1 "Test1 fails" # message not used if it succeeds

Mucho espacio para mejorar el diseño.

Los inconvenientes incluyen el hecho de que falseno es bonito (por lo tanto, el azúcar), y otras cosas que disparan la trampa pueden parecer un poco estúpidas. Aún así, me gusta este método.

Paul Hodges
fuente
4

Tiene 2 opciones: Redirigir la salida del script a un archivo, Introducir un archivo de registro en el script y

  1. Redirigir la salida a un archivo :

Aquí asume que el script genera toda la información necesaria, incluidos los mensajes de advertencia y error. A continuación, puede redirigir la salida a un archivo de su elección.

./runTests &> output.log

El comando anterior redirige tanto la salida estándar como la salida de error a su archivo de registro.

Con este enfoque, no es necesario introducir un archivo de registro en el script, por lo que la lógica es un poco más sencilla.

  1. Introduzca un archivo de registro en el script :

En su secuencia de comandos, agregue un archivo de registro codificándolo:

logFile='./path/to/log/file.log'

o pasándolo por un parámetro:

logFile="${1}"  # This assumes the first parameter to the script is the log file

Es una buena idea agregar la marca de tiempo en el momento de la ejecución al archivo de registro en la parte superior del script:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Luego puede redirigir sus mensajes de error al archivo de registro

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Esto agregará el error al archivo de registro y continuará la ejecución. Si desea detener la ejecución cuando ocurren errores críticos, puede exitel script:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Tenga en cuenta que exit 1indica que el programa detiene la ejecución debido a un error no especificado. Puede personalizar esto si lo desea.

Con este enfoque, puede personalizar sus registros y tener un archivo de registro diferente para cada componente de su secuencia de comandos.


Si tiene un script relativamente pequeño o desea ejecutar el script de otra persona sin modificarlo, el primer método es más adecuado.

Si siempre desea que el archivo de registro esté en la misma ubicación, esta es la mejor opción de la 2. Además, si ha creado una secuencia de comandos grande con varios componentes, es posible que desee registrar cada parte de manera diferente y el segundo enfoque es su único opción.

alamoot
fuente
3

A menudo encuentro útil escribir una función para manejar mensajes de error para que el código sea más limpio en general.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Esto toma el código de error del comando anterior y lo usa como el código de error predeterminado al salir de todo el script. También toma nota del tiempo, con microsegundos donde se admite (la fecha de GNU %Nes nanosegundos, que truncamos a microsegundos más tarde).

Si la primera opción es cero o un entero positivo, se convierte en el código de salida y lo eliminamos de la lista de opciones. Luego informamos el mensaje a error estándar, con el nombre del script, la palabra "ERROR" y el tiempo (usamos la expansión de parámetros para truncar nanosegundos a microsegundos, o para tiempos no GNU, truncar, por ejemplo, 12:34:56.%Na 12:34:56). Se agregan dos puntos y un espacio después de la palabra ERROR, pero solo cuando se proporciona un mensaje de error. Finalmente, salimos del script usando el código de salida previamente determinado, activando las trampas de forma normal.

Algunos ejemplos (suponga que el código reside en script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Adam Katz
fuente