¿Mejor práctica para usar $? en bash?

10

Cuando leo esta respuesta sobre $? otra pregunta me viene a la mente.

¿Existe alguna práctica recomendada sobre cómo usar $? en bash?


Veamos un ejemplo:

Tenemos un script lineal y me gustaría saber que todo el comando se ejecutó correctamente. ¿Crees que está bien llamar a una pequeña función (llamémosla "did_it_work"), para verificar el código de error y romperlo si no es así?

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

Este enfoque, por supuesto, significa que tengo que resolver manualmente el problema si hay alguno y volver a ejecutar el script.

¿Crees que es una buena idea o hay alguna otra mejor práctica para hacer esto?

/Gracias

Johan
fuente

Respuestas:

15

Una forma común es:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

entonces lo usas así:

mkdir -p some/path || die "mkdir failed with status $?"

O si desea que incluya el estado de salida, puede cambiarlo a:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

y luego usarlo es un poco más fácil:

mkdir -p some/path || die "mkdir failed"

Cuando falla, mkdires probable que ya haya emitido un mensaje de error, por lo que el segundo puede verse como redundante, y simplemente puede hacer:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(o use la primera variante de diearriba sin argumento)

En caso de que no lo haya visto command1 || command2antes, se ejecuta command1, y si command1falla, se ejecuta command2.

Entonces puedes leerlo como "crea el directorio o muere".

Su ejemplo se vería así:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

O puede alinearlo diesmás a la derecha para que el código principal sea más obvio.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

O en la siguiente línea cuando las líneas de comando son largas:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Además, si va a usar el nombre some/pathvarias veces, guárdelo en una variable para que no tenga que seguir escribiéndolo y puede cambiarlo fácilmente si lo necesita. Y cuando pase argumentos variables a comandos, asegúrese de usar el --delimitador de opciones para que el argumento no se tome como una opción si comienza con -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
Mikel
fuente
9

Podrías reescribir tu código así:

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

Si realmente no necesita registrar el código de error, pero solo si el comando tuvo éxito o no, puede acortar try()aún más de esta manera:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
Warren Young
fuente
También puede usar el código en este formato. <pre> prueba de función {
BillThor
Si usa esta funcionalidad en línea y no desea regresar del lado verdadero, puede reemplazarla return $?por la :incorporada.
BillThor
8

Si realmente quiere exitun error y está usando Bash, entonces también debe considerarlo set -e. De help set:

-e Salir inmediatamente si sale un comando con un estado distinto de cero.

Por supuesto, esto no le brinda la flexibilidad de una función did_it_work (), pero es una manera fácil de asegurarse de que su script bash se detenga en un error sin agregar muchas llamadas a su nueva función.

Steven D
fuente
set -ees útil. Hay algunos comandos que devuelven valores distintos de cero en circunstancias normales (por ejemplo diff). Cuando uso set -e en un script donde espero un retorno distinto de cero, lo hago command || true.
Shawn J. Goff
2
Además, incluso si lo usa set -e, puede establecer un "controlador de excepciones" para detectar todos los errores trap did_it_work EXIT.
Gilles 'SO- deja de ser malvado'
1
Esto es parte de POSIX, no una característica específica de bash. Úselo pero tenga en cuenta algunas dificultades mywiki.wooledge.org/BashFAQ/105
kmkaplan
@ ShawnJ.Goff prefiero hacer command && true. Así, el valor de retorno no se modifica.
kmkaplan
@kmkaplan Tu último comentario no tiene ningún sentido para mí. El propósito de command || truees evitar set -esalir del script si commanddevuelve un código de salida distinto de cero. Cambia el código de salida porque necesitamos hacerlo. Lo único que se command && truehace es ejecutar true(devolver un código de salida cero) si el comando `tuvo éxito (devolvió un código de salida cero): es un completo no operativo.
tripleee