Come posso ottenere questo script per uscire dall'errore in base al risultato di for loop?


13

Ho uno script bash che usa in set -o errexitmodo tale che per errore l'intero script esca nel punto di errore.
Lo script esegue un curlcomando che a volte non riesce a recuperare il file previsto, tuttavia quando ciò si verifica lo script non esce.

Ho aggiunto un forloop a

  1. mettere in pausa per alcuni secondi, quindi ripetere il curlcomando
  2. utilizzare falsenella parte inferiore del ciclo for per definire uno stato di uscita predefinito diverso da zero - se il comando arricciatura ha esito positivo - il ciclo si interrompe e lo stato di uscita dell'ultimo comando deve essere zero.
#! /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 .....

Il problema è quando il curlcomando fallisce, il ciclo ritenta il comando cinque volte - se tutti i tentativi falliscono il ciclo for termina e lo script principale riprende - invece di innescare il comando errexit.
Come posso ottenere la chiusura dell'intero script se questa curlaffermazione fallisce?

Risposte:


18

Sostituire:

done

con:

done || exit 1

Ciò causerà la chiusura del codice se il forciclo esce con un codice di uscita diverso da zero.

Come punto di curiosità, l' 1in exit 1non è necessario. Un exitcomando semplice uscirà con lo stato di uscita dell'ultimo comando eseguito che sarebbe false(codice = 1) se il download fallisce. Se il download ha esito positivo, il codice di uscita del loop è il codice di uscita del echocomando. echonormalmente esce con codice = 0, segnale positivo. In tal caso, ||non si attiva e il exitcomando non viene eseguito.

Infine, nota che set -o errexitpuò essere pieno di sorprese. Per una discussione dei suoi pro e contro, vedere le FAQ # 105 di Greg .

Documentazione

Da man bash:

per ((expr1; expr2; expr3)); fare la lista; done In
primo luogo, l'espressione aritmetica expr1 viene valutata secondo le regole descritte di seguito in VALUTAZIONE ARITMETICA. L'espressione aritmetica expr2 viene quindi valutata ripetutamente fino a quando non viene valutata su zero. Ogni volta che expr2 restituisce un valore diverso da zero, l'elenco viene eseguito e viene valutata l'espressione aritmetica expr3. Se viene omessa qualsiasi espressione, si comporta come se valuti 1. Il valore restituito è lo stato di uscita dell'ultimo comando nell'elenco eseguito o falso se una delle espressioni non è valida. [Enfasi aggiunta]


Pensi che sarebbe una buona idea mettere trueprima dell'istruzione break per essere espliciti e garantire il valore di uscita del ciclo?
RobertL,

1
Penso che esplicito sia meglio di implicito . Ecco perché ho scritto exit 1quando semplicemente exitavrebbe funzionato. È, tuttavia, una questione di stile e altri possono avere le proprie opinioni.
Giovanni 1024

1
funziona bene! grazie :) personalmente vorrei leggere exitcome una semplice uscita - che termina la sceneggiatura a sé stante. exit 1 mi leggerebbe come un "segnale" per qualche altro processo (cioè errexit) - che dovrebbe terminare lo script in base al "risultato" di exit 1. - quindi ci sono andato exitma grazie per la spiegazione
the_velour_fog

1
Se lo script esce da una condizione di errore, è necessario chiamare exit 1. Ciò non influisce errexitaffatto. Dice semplicemente al programma chiamante che qualcosa è andato storto. Il falsecomando contiene una dichiarazione: exit(1). Il 99,9% dei comandi Unix restituisce 0 in caso di successo e diverso da zero in caso di errore. Anche il tuo dovrebbe.
RobertL,

2

Se hai errexitimpostato, l' falseistruzione dovrebbe causare la chiusura immediata dello script. Stessa cosa se il curlcomando fallisce.

Lo script di esempio, come scritto, dovrebbe uscire dopo il primo curlerrore di comando la prima volta che chiama falsese è impostato errexit.

Per vedere come funziona (uso la scorciatoia -eper impostare errexit:

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

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

Quindi se il curl comando viene eseguito più di una volta, questo script non è stato errexitimpostato.


1
set -eè più sottile di così. Sarà Non uscita dopo il primo comando fallito in un ciclo. Puoi dimostrarlo eseguendo (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )e notando che il codice viene eseguito falsequattro volte. Per ulteriori informazioni set -e, vedere le FAQ # 105 di Greg .
Giovanni 1024

@ John1024 Grazie. Questo sta andando giù e giù.
RobertL,

@ John1024 Ma suppongo che le prove non siano ancora errexitstate stabilite. Si prega di applicare la logica allo script nella domanda. Esegui questo: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Sì, testare i valori di ritorno con if while || &&etc non attiva errexit. Lo script originale non ha fatto ||il ciclo for.
RobertL,

Ho appena notato che non avevo mostrato il set -o errexitcomando nel mio codice di esempio, l'ho aggiunto ora - e per me non è stato un errore uscire dal previsto. Avevo bisogno di mantenere l' falseultimo come comando nel ciclo for, quindi chiudere il ciclo con done || exit [1]- quindi ha funzionato bene!
the_velour_fog

@RobertL Vedo il tuo punto.
Giovanni 1024

1

set -o errexit può essere complicato in loop e subshells, perché è necessario tornare indietro nel processo.

Rompere un loop (anche durante il normale funzionamento) è considerato una cattiva pratica. Puoi chiamarmi vecchia scuola per preferire un ciclo continuo piuttosto che un ciclo continuo per due condizioni, ma trovo che sia meglio leggere:

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

0

Se errexitè impostato e il curlcomando non riesce, lo script termina subito dopo il comando di arricciatura non riuscito. Nel manuale di bash non c'è nessun suggerimento che set -eignori qualsiasi stato di ritorno fallito di un singolo in un comando composto. Questo sarebbe solo il caso se il comando composto viene eseguito in un contesto in cui set -eviene ignorato.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Prova un esempio leggermente adattato pubblicato da RobertL. Questo si ferma alla prima for-iteration subito dopo il falso comando:

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

0

Puoi semplicemente aggiungere l'opzione --fail al comando curl, questo risolverà il tuo problema, lo script fallirà e uscirà in caso di errore se il comando curl fallisce, anche se molto utile quando usi curl nella pipeline jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.