¿Cómo detectar un error en un script de bash de Linux?

Hice el siguiente script:

# !/bin/bash # OUTPUT-COLORING red="\e[0;31m" green="\e[0;32m" NC="\e[0m" # No Color # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { cd $1 if [ $? = 0 ] then echo -e "${green}$1${NC}" else echo -e "${red}$1${NC}" fi } # EXE directoryExists "~/foobar" directoryExists "/www/html/drupal" 

El script funciona, pero al lado de mi se repite, también existe la salida cuando

cd $1 

falla en la ejecución.

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory 

¿Es posible captar esto?

Comentarios

  • Solo un FYI, también puede hacer esto mucho más simple; test -d /path/to/directory (o [[ -d /path/to/directory ]] en bash) le dirá si un objetivo determinado es un directorio o no, y lo hará en silencio.
  • @Patrick, que solo prueba si es ‘ un directorio, no si puedes cd en él.
  • @StephaneChazelas sí. El nombre de la función es directoryExists.
  • Vea una respuesta detallada aquí: Generar error en un script Bash .

Respuesta

Su script cambia de directorio mientras se ejecuta, lo que significa que no funcionará con una serie de nombres de ruta relativos. Luego, comentó más tarde que solo deseaba verificar la existencia del directorio, no la capacidad de usar cd, por lo que las respuestas no necesitan usar cd en absoluto. Revisado. Usando tput y colores de man terminfo:

#!/bin/bash -u # OUTPUT-COLORING red=$( tput setaf 1 ) green=$( tput setaf 2 ) NC=$( tput setaf 0 ) # or perhaps: tput sgr0 # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { # was: do the cd in a sub-shell so it doesn"t change our own PWD # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then if [ -d "$1" ] ; then # was: echo "${green}$1${NC}" printf "%s\n" "${green}$1${NC}" else # was: echo "${red}$1${NC}" printf "%s\n" "${red}$1${NC}" # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}" fi } 

(Editado para usar el printf más invulnerable en lugar del echo problemático que podría actuar sobre las secuencias de escape en el texto.)

Comentarios

  • Eso también corrige (a menos que xpg_echo esté activado) los problemas cuando los nombres de archivo contienen caracteres de barra invertida.

Respuesta

Use set -e para establecer el modo de salida en caso de error: si un comando simple devuelve un estado distinto de cero (lo que indica falla), el shell sale.

Tenga en cuenta que set -e no siempre se activa. Los comandos en las posiciones de prueba pueden fallar (por ejemplo, if failing_command, failing_command || fallback). Los comandos en el subshell solo conducen a salir del subshell, no del padre: set -e; (false); echo foo muestra foo.

Alternativamente, o además, en bash (y ksh y zsh, pero no sh simple), puede especificar un comando que «se ejecute en caso de que un comando devuelva un estado distinto de cero, con la trampa ERR, p. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Tenga en cuenta que en casos como (false); …, la trampa ERR se ejecuta en el subshell, por lo que no puede hacer que el padre salga.

Comentarios

  • Recientemente experimenté un poco y descubrí una forma conveniente de corregir el comportamiento de ||, que permite manejar fácilmente los errores correctamente sin usar trampas. Ver mi respuesta . ¿Qué opinas de ese método?
  • @ sam.kozin No ‘ t tienes tiempo para revisar tu respuesta en detalle, se ve bien en principio. Aparte de la portabilidad, ¿cuáles son los beneficios sobre ksh / bash / zsh ‘ s ERR trap?
  • Probablemente el único beneficio sea la componibilidad, ya que no ‘ t se arriesga a sobrescribir otra trampa que se estableció antes de que se ejecute la función. Lo cual es una característica útil cuando ‘ reescribiendo alguna función común que luego obtendrá y usará de otros scripts. Otro beneficio puede ser compatibilidad total con POSIX, aunque no es tan importante como la pseudo-señal ERR es compatible con todas las shells principales. ¡Gracias por la reseña! =)
  • @ sam.kozin Olvidé escribir en mi comentario anterior: es posible que desee publicar esto en Revisión de código y publicar una en la sala de chat .
  • Gracias por la sugerencia, ‘ intentaré seguir eso. ¿No ‘ no sabía nada sobre la revisión de código.

Responder

Para ampliar la @Gilles «respuesta :

De hecho, set -e no funciona dentro de los comandos si usa el operador || después de ellos, incluso si los ejecuta en una subcapa; por ejemplo, esto no funcionaría:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_1.sh: line 16: some_failed_command: command not found # <-- inner # <-- outer set -e outer() { echo "--> outer" (inner) || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

Pero || es necesario el operador para evitar el retorno de la función externa antes de la limpieza.

Hay un pequeño truco que se puede usar para solucionar esto: ejecute el comando interno en segundo plano y luego inmediatamente espéralo.El wait incorporado devolverá el código de salida del comando interno, y ahora «estás usando || después de wait, no la función interna, por lo que set -e funciona correctamente dentro de esta última:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_2.sh: line 27: some_failed_command: command not found # --> cleanup set -e outer() { echo "--> outer" inner & wait $! || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

Aquí está la función genérica que se basa en esta idea. Debería funcionar en todos los shells compatibles con POSIX si elimina local palabras clave, es decir, reemplace todas las local x=y con solo x=y:

 # [CLEANUP=cleanup_cmd] run cmd [args...] # # `cmd` and `args...` A command to run and its arguments. # # `cleanup_cmd` A command that is called after cmd has exited, # and gets passed the same arguments as cmd. Additionally, the # following environment variables are available to that command: # # - `RUN_CMD` contains the `cmd` that was passed to `run`; # - `RUN_EXIT_CODE` contains the exit code of the command. # # If `cleanup_cmd` is set, `run` will return the exit code of that # command. Otherwise, it will return the exit code of `cmd`. # run() { local cmd="$1"; shift local exit_code=0 local e_was_set=1; if ! is_shell_attribute_set e; then set -e e_was_set=0 fi "$cmd" "$@" & wait $! || { exit_code=$? } if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then set +e fi if [ -n "$CLEANUP" ]; then RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@" return $? fi return $exit_code } is_shell_attribute_set() { # attribute, like "x" case "$-" in *"$1"*) return 0 ;; *) return 1 ;; esac }  

Ejemplo de uso:

 #!/bin/sh set -e # Source the file with the definition of `run` (previous code snippet). # Alternatively, you may paste that code directly here and comment the next line. . ./utils.sh main() { echo "--> main: $@" CLEANUP=cleanup run inner "$@" echo "<-- main" } inner() { echo "--> inner: $@" sleep 0.5; if [ "$1" = "fail" ]; then oh_my_god_look_at_this fi echo "<-- inner" } cleanup() { echo "--> cleanup: $@" echo " RUN_CMD = "$RUN_CMD"" echo " RUN_EXIT_CODE = $RUN_EXIT_CODE" sleep 0.3 echo "<-- cleanup" return $RUN_EXIT_CODE } main "$@"  

Ejecutando el ejemplo:

 $ ./so_3 fail; echo "exit code: $?" --> main: fail --> inner: fail ./so_3: line 15: oh_my_god_look_at_this: command not found --> cleanup: fail RUN_CMD = "inner" RUN_EXIT_CODE = 127 <-- cleanup exit code: 127 $ ./so_3 pass; echo "exit code: $?" --> main: pass --> inner: pass <-- inner --> cleanup: pass RUN_CMD = "inner" RUN_EXIT_CODE = 0 <-- cleanup <-- main exit code: 0  

El Lo único que debe tener en cuenta al usar este método es que todas las modificaciones de las variables de Shell realizadas desde el comando com Si pasa a run no se propagará a la función de llamada, porque el comando se ejecuta en una subcapa.

Respuesta

No dice exactamente qué quiere decir con catch — informe y continúe; ¿cancelar el procesamiento posterior?

Dado que cd devuelve un estado distinto de cero en caso de error, puede hacer:

cd -- "$1" && echo OK || echo NOT_OK 

Simplemente puede salir en caso de falla:

cd -- "$1" || exit 1 

O, repita su propio mensaje y salga:

cd -- "$1" || { echo NOT_OK; exit 1; } 

Y / o suprimir el error proporcionado por cd en caso de falla:

cd -- "$1" 2>/dev/null || exit 1 

Según los estándares, los comandos deben poner mensajes de error en STDERR (descriptor de archivo 2). Por lo tanto, 2>/dev/null dice redirigir STDERR al «bit-bucket» conocido por /dev/null.

(no olvide para citar sus variables y marcar el final de las opciones para cd).

Comentarios

  • @Stephane El punto de Chazelas de citar y señalar el final de las opciones está bien tomado. Gracias por editar.

Responder

En realidad para su caso, diría que la lógica se puede mejorar.

En lugar de cd y luego verifique si existe, verifique si existe y luego ingrese al directorio.

if [ -d "$1" ] then printf "${green}${NC}\\n" "$1" cd -- "$1" else printf "${red}${NC}\\n" "$1" fi 

Pero si su propósito es silenciar los posibles errores, entonces cd -- "$1" 2>/dev/null, pero esto hará que la depuración en el futuro sea más difícil. Puede verificar las pruebas if banderas en: Bash if documentación :

Comentarios

  • Esta respuesta no cite la $1 variable y fallará si esa variable contiene espacios en blanco u otros metacaracteres de shell. Tampoco verifica si el usuario tiene permiso para cd en él.
  • En realidad estaba tratando de verificar si un directorio determinado existía, no necesariamente cd a él. . Pero debido a que no ‘ no lo sabía mejor, pensé que intentar acceder a él causaría un error si no existía, ¿por qué no detectarlo? No ‘ no sabía sobre el if [-d $ 1] que ‘ es exactamente lo que necesitaba. ¡Muchas gracias! (Yo ‘ m usado para programar Java, y la búsqueda de un directorio en una declaración if no es exactamente común en Java)

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *