Jak warunkowo zrobić coś, jeśli polecenie zakończyło się powodzeniem lub niepowodzeniem


283

Jak mogę zrobić coś takiego w bash?

if "`command` returns any error";
then
    echo "Returned an error"
else
    echo "Proceed..."
fi

Odpowiedzi:


359

Jak warunkowo zrobić coś, jeśli polecenie zakończyło się powodzeniem lub niepowodzeniem

Właśnie to ifrobi oświadczenie bash :

if command ; then
    echo "Command succeeded"
else
    echo "Command failed"
fi

Dodawanie informacji z komentarzy: w tym przypadku nie musisz używać składni [... sam w sobie jest poleceniem, prawie równoważnym z . Jest to prawdopodobnie najczęstsze polecenie używane w komendzie , co może prowadzić do założenia, że ​​jest to część składni powłoki. Ale jeśli chcesz sprawdzić, czy polecenie zakończyło się powodzeniem, użyj samego polecenia bezpośrednio , jak pokazano powyżej.][testifif

Edycja: zacytowałem pytanie u góry dla jasności (ta odpowiedź nie pojawia się u góry strony).


11
Zauważ, że średnik jest ważny.
Thorbjørn Ravn Andersen

21
Lub możesz po prostu umieścić thenw osobnej linii.
l0b0

36
@Joe: Myślę, że masz na myśli if ! command ; then ... ; fi. [sam w sobie jest poleceniem i w tym przypadku nie jest potrzebny.
Keith Thompson

20
@Joe: Moja droga ma również tę zaletę, że jest poprawna. if [ ! command ]nie wykonuje command; traktuje commandjak ciąg i traktuje to jako prawdziwe, ponieważ ma niezerową długość. [jest synonimem testpolecenia
Keith Thompson

5
Ups Masz rację.
Joe

133

W przypadku drobnych rzeczy, które mają się zdarzyć, jeśli polecenie powłoki działa, możesz użyć &&konstrukcji:

rm -rf somedir && trace_output "Removed the directory"

Podobnie w przypadku drobnych rzeczy, które mają się zdarzyć, gdy polecenie powłoki nie powiedzie się, możesz użyć ||:

rm -rf somedir || exit_on_error "Failed to remove the directory"

Lub obydwa

rm -rf somedir && trace_output "Removed the directory" || exit_on_error "Failed to remove the directory"

Prawdopodobnie nierozsądnie jest robić wiele z tymi konstruktami, ale czasami mogą sprawić, że przepływ kontroli będzie znacznie wyraźniejszy.


3
Są krótsze i (przynajmniej w niektórych pociskach) szybsze. Drżę na myśl o potwornym skrypcie instalacyjnym Ultrix napisanym tylko tymi konstrukcjami warunkowymi, które kiedyś próbowałem rozszyfrować ...
vonbrand

101

Sprawdź wartość $?, która zawiera wynik wykonania ostatniego polecenia / funkcji:

#!/bin/bash

echo "this will work"
RESULT=$?
if [ $RESULT -eq 0 ]; then
  echo success
else
  echo failed
fi

if [ $RESULT == 0 ]; then
  echo success 2
else
  echo failed 2
fi

11
Chociaż technicznie poprawny (a tym samym nie uzasadnia opinii negatywnej), nie wykorzystuje ifidiomu Basha . Wolę odpowiedź Keitha Thompsona.
janmoesen

16
Ten idiom ma zalety - zachowuje wartość zwracaną. W sumie uważam, że ten jest silniejszy, choć bardziej szczegółowy. jest również łatwiejszy do odczytania.
taxilian

2
Co to jest „Bash's if idiom”?
Nowaker

3
@Nowaker Fakt, że jedynym celem ifjest to zrobić. Warunki kontroli przepływu w Bash sprawdzają się $?za kulisami; właśnie to robią. Jawne badanie jego wartości powinno być niepotrzebne w zdecydowanej większości przypadków i zwykle stanowi dla początkującego przeciwnika.
tripleee

1
@lunakid Jeśli nie obchodzi Cię kod wyjścia, if ! cmdjest w porządku. W przeciwnym razie powszechnym rozwiązaniem jest użycie elseklauzuli. Nie możesz mieć pustego, thenale czasami widzisz, then : nothing; elsegdzie :brak op jest znaczący. trueteż by tam działał.
tripleee

47

To działało dla mnie:

command && echo "OK" || echo "NOK"

jeśli się commandpowiedzie, echo "OK"zostanie wykonany, a ponieważ się powiedzie, wykonanie się tam kończy. W przeciwnym razie &&jest pomijany i echo "NOK"wykonywany.


5
Jeśli chcesz coś zrobić, jeśli się nie powiedzie, i zachowaj kod wyjścia (aby wyświetlić go w wierszu polecenia lub przetestować w skrypcie), możesz to zrobić: command && echo "OK" || c=$?; echo "NOK"; $(exit $c)
Sam Hasler

2
@ Sam-Hasler: czy nie powinno tak być command && echo "OK" || (c=$?; echo "NOK"; (exit $c))?
jrw32982

9
Ponadto, jeśli echo "OK"część sama mogłaby zawieść, to lepiej:command && (echo "OK"; exit 0) || (c=$?; echo "NOK"; (exit $c))
jrw32982

@ jrw32982, Nicea, użyłem poprzedniej konstrukcji, ale nie drugiej.
Sam Hasler,

Prawdziwa odpowiedź znajduje się w komentarzach, dziękuję wszystkim!
Joshua Pinter

6

Należy zauważyć, że if...then...fii &&/ lub ||typ podejścia dotyczy statusu wyjścia zwracanego przez polecenie, które chcemy przetestować (0 w przypadku powodzenia); jednak niektóre polecenia nie zwracają niezerowego statusu wyjścia, jeśli polecenie zakończyło się niepowodzeniem lub nie mogło poradzić sobie z danymi wejściowymi. Oznacza to, że zwykłe ifi &&/ ||podejścia nie będą działać dla tych konkretnych poleceń.

Na przykład w systemie Linux GNU filenadal wychodzi z 0, jeśli otrzymał nieistniejący plik jako argument i findnie mógł zlokalizować określonego pliku użytkownika.

$ find . -name "not_existing_file"                                          
$ echo $?
0
$ file ./not_existing_file                                                  
./not_existing_file: cannot open `./not_existing_file' (No such file or directory)
$ echo $?
0

W takich przypadkach jednym potencjalnym sposobem na poradzenie sobie z sytuacją jest odczyt stderr/ stdinkomunikaty, np. Te, które zwróciły filepolecenie lub parsują dane wyjściowe polecenia jak w find. W tym celu casemożna użyć instrukcji.

$ file ./doesntexist  | while IFS= read -r output; do                                                                                                                  
> case "$output" in 
> *"No such file or directory"*) printf "%s\n" "This will show up if failed";;
> *) printf "%s\n" "This will show up if succeeded" ;;
> esac
> done
This will show up if failed

$ find . -name "doesn'texist" | if ! read IFS= out; then echo "File not found"; fi                                                                                     
File not found

2

Najbardziej podatny na błędy, jaki mogłem wymyślić, to:

  • Najpierw uzyskaj wartość. Załóżmy, że robisz coś takiego:

RR=$?

Teraz, nie tylko dla tej sytuacji, ale dla innych, z którymi możesz się spotkać, rozważ:

zdefiniowana zmienna:

$ AA=1 ; if (( "10#0${AA}" == 1 )) ; then echo yes ; else echo no ; fi

Odpowiedź: tak

$ AA=1 ; if (( "10#0${AA}" != 1 )) ; then echo yes ; else echo no ; fi

Odpowiedź: nie

niezdefiniowana zmienna:

$ AA=1 ; if (( "10#0${BB}" == 1 )) ; then echo yes ; else echo no ; fi

Odpowiedź: nie

$ AA=1 ; if (( "10#0${BB}" != 1 )) ; then echo yes ; else echo no ; fi

Odpowiedź: tak

$ AA=1 ; if (( "10#0${BB}" == 0 )) ; then echo yes ; else echo no ; fi

Odpowiedź: tak

Zapobiega to wszelkiego rodzaju błędom.

Prawdopodobnie znasz całą składnię, ale oto kilka wskazówek:

  • Użyj cytatów. Unikaj "blank"być nothing.
  • Nowa nowoczesna notacja dla zmiennych to ${variable}.
  • Dodanie zera skonkatenowanego przed liczbą również pozwoli uniknąć „braku numeru”.
  • Ale poczekaj, dodanie zera powoduje, że liczba staje się base-8. Pojawi się błąd, taki jak:
    • value too great for base (error token is "08")dla liczb powyżej 7. Właśnie wtedy 10#wchodzi w grę:
    • 10#wymusza, aby liczba była base-10.

1

Możesz to zrobić:

if ($( ping 4.4.4.4 -c1 > /dev/null )) ; then
  echo "ping response succsess!!!"
fi

9
To działa, ale jest zawiłe. Używasz polecenia ping w podpowłoce podpowłoki, dane wyjściowe pingsą przechwytywane w celu uruchomienia jako polecenia. Ale ponieważ dane wyjściowe są przekierowywane do / dev / null, który zawsze będzie pustym ciągiem. Więc nic nie uruchamiasz w podpowłoce, co oznacza, że ​​poprzedni status wyjścia (podpowłoki zastępowania poleceń, czyli ping) zostanie zachowany. Oczywiście poprawna droga jest if ping ...; thentutaj.
Stéphane Chazelas

1

Jak zauważono w innym miejscu tego wątku, pierwotne pytanie w zasadzie odpowiada samo. Oto ilustracja pokazująca, że ifwarunki mogą być również zagnieżdżone.

W tym przykładzie użyto ifdo sprawdzenia, czy plik istnieje i czy jest to zwykły plik. Jeśli te warunki są spełnione, sprawdź, czy ma rozmiar większy niż 0.

#!/bin/bash

echo "Which error log are you checking today? "
read answer

if [ -f /opt/logs/$answer*.errors ]
    then
        if [ -s /opt/logs/$answer*.errors ]
            then
                echo "Content is present in the $answer error log file."
            else
                echo "No errors are present in the $answer error log file."
        fi
    else
        echo "$answer does not have an error log at this time."
fi

2
Tak robi twoja odpowiedź, ale twoja odpowiedź nie odnosi się do pytania.
Jeff Schaller

@JeffSchaller, dziękuję za notatkę. Zredagowałem swój post, aby zamieścić odniesienie do pytania.
kwarcowo


-3

Można to zrobić po prostu w ten sposób, ponieważ $?daje status ostatniego wykonanego polecenia.

Tak może być

#!/bin/sh

... some command ...

if [ $? == 0 ] ; then
  echo '<the output message you want to display>'
else 
  echo '<failure message>'
fi

1
Downvote: To po prostu parafrazuje wcześniejszą odpowiedź, która słusznie spotkała się z krytyką za to, że jest jednodiomatyczna.
tripleee
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.