Jak wyjść ze skryptu powłoki, jeśli jedna jego część zawiedzie?

51

Jak napisać skrypt powłoki, który kończy działanie, jeśli jedna jego część zawiedzie? Na przykład jeśli poniższy fragment kodu nie powiedzie się, skrypt powinien zakończyć działanie.

n=0
until [ $n -ge 5 ]
do
  gksu *command* && break
  n=$[$n+1]
  sleep 3
Weylyn
źródło

Odpowiedzi:

88

Jednym z podejść byłoby dodanie set -ena początku skryptu. Oznacza to (z help set):

  -e  Exit immediately if a command exits with a non-zero status.

Jeśli więc któreś z twoich poleceń się nie powiedzie, skrypt zakończy działanie.

Alternatywnie możesz dodać wyraźne exitinstrukcje w możliwych punktach awarii:

command || exit 1
terdon
źródło
5
Ja (i bash wiki ) wolałbym, żeby ludzie zastanawiali się nad poprawną obsługą błędów, zamiast używać funkcji (zepsutej przez projekt IMO) set -e. Jednak tak naprawdę nie ma tutaj zastosowania. OP chce wyjść ze skryptu po 5 nieudanych próbach uruchomienia polecenia.
Stéphane Chazelas,
1
@ StéphaneChazelas Nie będę się z tobą kłócił, czy jest zepsuty, jestem pewien, że masz rację. Jednak OP zapytał: „Jak napisać skrypt powłoki, który kończy działanie, jeśli jedna jego część zawiedzie?”, Co sprawia, że ​​myślisz, że chodzi o wyjście po 5 błędach?
terdon
ponieważ nie mogę wymyślić żadnego innego sposobu interpretacji pytania.
Stéphane Chazelas,
1
@ StéphaneChazelas możesz mieć rację. Zinterpretowałem to dosłownie: jak cały skrypt może wyjść, jeśli jakaś jego część zawiedzie. I set -eto jedyny sposób, w jaki mogę to zrobić.
terdon
W tym fragmencie skryptu komendy, które można uruchomić, set -eto sleep( breakspecjalne wbudowanie spowoduje, że skrypt zakończy działanie po awarii w większości powłok, na komendy w iflub po lewej stronie &&nie ma wpływu set -e, n=...może się nie powieść, jeśli njest tylko do odczytu, ale wtedy bez niego set -erównież wyjdzie ze skryptu ), więc interpretacja wydaje się mało prawdopodobna. Zgadzam się, że pytanie jest źle sformułowane.
Stéphane Chazelas,
17

Możesz wyjść ze skryptu w dowolnym miejscu za pomocą słowa kluczowego exit. Możesz także podać kod zakończenia, aby wskazać innym programom, że skrypt nie powiódł się, np. exit 1Lub exit 2itp. (Zgodnie z konwencją kod wyjścia 0 oznacza sukces, a wartość większa od 0 oznacza niepowodzenie; jednak również zgodnie z konwencją wyjście kody powyżej 127 są zarezerwowane dla nienormalnego zakończenia (np. przez sygnał)).

Ogólna konstrukcja wyjścia w przypadku awarii jest

if [ failure condition ]; then
    exit n
fi

z odpowiednim failure conditioni n. Ale w określonych scenariuszach możesz postępować inaczej. Teraz dla twojej sprawy interpretuję twoje pytanie, że jeśli któreś z pięciu wezwań gksunie powiedzie się, to masz zamiar wyjść. Jednym ze sposobów jest użycie takiej funkcji

function try_command {
    for i in 1 2 3 4 5 ; do
        if gksu command ; then
            return 0
        fi
    fi
    exit 1
}

a następnie wywołaj pętlę przez try_command.

Istnieją (bardziej) zaawansowane lub wyrafinowane sposoby rozwiązania twojego pytania. Jednak powyższe rozwiązanie jest bardziej dostępne dla początkujących niż, powiedzmy, rozwiązanie Stephane.

przeciwdziałanie
źródło
10
attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exitkończy skrypt, chyba że jest wywoływany w podpowłoce. Jeśli ta część skryptu jest w podpowłoce, na przykład dlatego, że znajduje się wewnątrz (...)lub $(...)lub część rurociągiem, wtedy wyjść tylko , że powłoki w tle .

W takim przypadku, jeśli chcesz, aby skrypt zakończył działanie oprócz podpowłoki, musisz wywołać exitwyjście z tej podpowłoki.

Na przykład tutaj z 2 zagnieżdżonymi poziomami podpowłoki:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

Może stać się trudniejsze, jeśli podpowłoka jest częścią potoku. bashposiada specjalną $PIPESTATUStablicę, podobną do zsh„s $pipestatusjednym, które mogą Ci pomóc tutaj:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi
Stéphane Chazelas
źródło
3

Pułapka wykona akcję po otrzymaniu sygnału.

trap "echo EXIT;  exit" 0
trap "echo HUP;   exit" 1
trap "echo CTL-C; exit" 2
trap "echo QUIT;  exit" 3
trap "echo ERR;   exit" ERR
n=0
until [ $n -ge 5 ]
do
  n=$[$n+1]
  echo $n
  sleep 3
done

Uruchom to i pozwól mu wyjść normalnie. Zatrzymuje sygnał 0.

EXIT

Uruchom go ponownie i przerwij za pomocą ^ C. Przechwytuje sygnał 2 i sygnał 0.

CTL-C
EXIT

Niezerowy status wyjścia będzie pułapką na ERR

ERR
EXIT
RobP
źródło