¿Cómo puedo hacer que este script salga por error en función del resultado del ciclo for?

13

Tengo un script bash que utiliza set -o errexitpara que, en caso de error, todo el script salga en el punto de falla.
El script ejecuta un curlcomando que a veces no puede recuperar el archivo deseado; sin embargo, cuando esto ocurre, el script no sale por error.

He agregado un forbucle a

  1. pausa por unos segundos y luego vuelve a intentar el curlcomando
  2. use falseen la parte inferior del bucle for para definir un estado de salida predeterminado distinto de cero, si el comando curl tiene éxito, el bucle se rompe y el estado de salida del último comando debería ser cero.
#! /bin/bash

set -o errexit

# ...

for (( i=1; i<5; i++ ))
do
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    if [ -f ~/.vim/autoload/pathogen.vim ]
    then
        echo "file has been retrieved by curl, so breaking now..."
        break;
    fi

    echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
    sleep 5
    # exit with non-zero status so main script will errexit
    false

done

# rest of script .....

El problema es cuando el curlcomando falla, el ciclo vuelve a intentarlo cinco veces, si todos los intentos no tienen éxito, el ciclo for finaliza y se reanuda el script principal, en lugar de activar el errexit.
¿Cómo puedo hacer que salga el script completo si esta curldeclaración falla?

the_velour_fog
fuente

Respuestas:

18

Reemplazar:

done

con:

done || exit 1

Esto hará que el código salga si el forbucle sale con un código de salida distinto de cero.

Como punto de trivia, el 1de exit 1no es necesario. Un exitcomando simple saldría con el estado de salida del último comando ejecutado que sería false(código = 1) si falla la descarga. Si la descarga se realiza correctamente, el código de salida del bucle es el código de salida del echocomando. echonormalmente sale con código = 0, significativamente éxito. En ese caso, el ||no se dispara y el exitcomando no se ejecuta.

Por último, tenga en cuenta que set -o errexitpuede estar lleno de sorpresas. Para una discusión de sus pros y sus contras, consulte las preguntas frecuentes de Greg # 105 .

Documentación

De man bash:

para ((expr1; expr2; expr3)); hacer la lista; hecho
Primero, la expresión aritmética expr1 se evalúa de acuerdo con las reglas que se describen a continuación en EVALUACIÓN ARITMÉTICA. La expresión aritmética expr2 se evalúa repetidamente hasta que se evalúa a cero. Cada vez que expr2 se evalúa a un valor distinto de cero, se ejecuta la lista y se evalúa la expresión aritmética expr3. Si se omite alguna expresión, se comporta como si se evaluara a 1. El valor de retorno es el estado de salida del último comando en la lista que se ejecuta, o falso si alguna de las expresiones no es válida. [Énfasis añadido]

John1024
fuente
¿Crees que sería una buena idea poner trueantes de la declaración de ruptura para ser explícito y garantizar el valor de salida del bucle?
RobertL
1
Creo que explícito es mejor que implícito . Es por eso que escribí exit 1cuando simplemente exithubiera funcionado. Sin embargo, es una cuestión de estilo y otros pueden tener sus propias opiniones.
John1024
1
funciona bien! gracias :) personalmente, lo leería exitcomo una salida simple, que termina el script por derecho propio. exit 1 me leería como una "señal" a algún otro proceso (es decir errexit), que debería terminar el script en función del "resultado" de exit 1. - así que he ido exitpero gracias por la explicación
the_velour_fog
1
Si su script se está cerrando debido a una condición de error, debe llamar exit 1. Eso no afecta errexiten absoluto. Simplemente le dice al programa de llamadas que algo salió mal. El falsecomando contiene una declaración: exit(1). El 99.9% de los comandos de Unix devuelven 0 en caso de éxito y no cero en caso de error. El tuyo también debería.
RobertL
2

Si lo ha errexitconfigurado, la falsedeclaración debería hacer que el script salga inmediatamente. Lo mismo si el curlcomando falla.

Su script de ejemplo, tal como está escrito, debería salir después del primer curlfallo del comando la primera vez que llama falsesi se establece errexit.

Para ver cómo funciona (uso la abreviatura -epara configurar errexit:

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

Entonces, si el curlcomando se ejecuta más de una vez, este script no se ha errexitconfigurado.

RobertL
fuente
1
set -eEs más sutil que eso. Será no salir después del comando primera fallado en un bucle. Puede probarlo usted mismo al ejecutar (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )y observar que el código se ejecuta falsecuatro veces. Para más información set -e, consulte las preguntas frecuentes de Greg # 105 .
John1024
@ John1024 Gracias. Este va bajando y bajando.
RobertL
@ John1024 Pero supongo que la evidencia aún errexitno se estableció. Aplique la lógica al script en la pregunta. Ejecute esto: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Sí, probar los valores de retorno con if while || &&etc. no activa errexit. El guión original no incluía ||el bucle for.
RobertL
Acabo de notar que no había mostrado el set -o errexitcomando en mi código de ejemplo, lo he agregado ahora, y para mí no fue un error al salir como se esperaba. Necesitaba mantener el falsecomo el último comando en el bucle for, luego cerrar el bucle con done || exit [1]- ¡entonces funcionó bien!
the_velour_fog
@RobertL veo tu punto.
John1024
1

set -o errexit puede ser complicado en bucles y subcapas, porque tienes que volver a salir del proceso.

Romper un bucle (incluso en funcionamiento normal) se considera una mala práctica. Puedes llamarme de la vieja escuela para preferir un ciclo while en lugar de un ciclo for por dos condiciones, pero me parece mejor leerlo:

i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
    [ $i -eq 1 ] || sleep 5
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    RET=$?
    i=$((i+1))
done
exit $RET
rexkogitans
fuente
0

Si errexitestá configurado y el curlcomando falla, el script termina justo después del comando curl fallido. En el manual de bash no hay ninguna pista que set -eignore cualquier estado de retorno fallido de un solo comando compuesto. Este sería solo el caso si el comando compuesto se ejecuta en un contexto donde set -ese ignora.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Pruebe un ejemplo ligeramente adaptado publicado por RobertL. Esto se detiene en la primera iteración justo después del comando falso:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )
G32RW
fuente
0

Simplemente puede agregar la opción --fail al comando curl, esto resolverá su problema, el script fallará y saldrá por error si el comando curl falla, si es muy útil también cuando se usa curl en la tubería jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
DevOps-Eng
fuente