Są, jeśli indziej, równoważne z logicznym i && lub || a gdzie powinienem preferować jeden od drugiego?


27

Uczę się o strukturach decyzyjnych i natrafiłem na następujące kody:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Oba zachowują się tak samo. Czy są jakieś zalety korzystania z jednej drogi od drugiej?



3
Nie równoważne. Zobacz doskonałą odpowiedź Ikara. Na przykład rozważmy przypadek, w którym plik ./myfile istnieje, ale nie można go odczytać.
AlexP


Odpowiedzi:


26

Nie, konstrukcje if A; then B; else C; fii nieA && B || Crównoważne .

Za pomocą if A; then B; else C; fikomenda Ajest zawsze analizowana i wykonywana (przynajmniej podejmowana jest próba jej wykonania), a następnie komenda Blub komenda Csą oceniane i wykonywane.

Dzięki A && B || C, to samo dla poleceń Ai Bjednak różne dla C: polecenia Cjest analizowany i wykonywany jeśli obaj A się nie powiedzie lub B nie.

W swoim przykładzie załóżmy, że ci się uda, chmod u-r ./myfilepomimo [ -f ./myfile ]sukcesucat /home/user/myfile

Moja rada: używaj A && Blub A || Bile chcesz, pozostaje to łatwe do odczytania i zrozumienia i nie ma pułapki. Ale jeśli masz na myśli, jeśli ... to ... jeszcze ... wtedy użyj if A; then B; else C; fi.


29

Większości ludzi łatwiej jest zrozumieć if... then... else... fiformę.

Albowiem a && b || c, trzeba mieć pewność, że bpowróci prawda. Jest to przyczyną subtelnych błędów i jest dobrym powodem, aby unikać tego stylu. Jeśli b nie zwróci prawdy, to nie są takie same.

 $ if true; then false ; else echo boom ; fi
 $ true && false || echo boom
 boom

W przypadku bardzo krótkich testów i działań, które nie mają klauzuli else, skrócona długość jest atrakcyjna, np

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&&i ||short circuiting operators, jak tylko wynik będzie znany, dalsze niepotrzebne testy są pomijane. a && b || cjest zgrupowane jako (a && b) || c. Pierwszy ajest uruchamiany. Jeśli failszdefiniowano, że nie zwraca statusu wyjścia 0, grupa (a && b)jest znana faili bnie trzeba jej uruchamiać. ||Nie wie wynikiem ekspresji więc musi wykonać c. Jeśli się apowiedzie (zwraca zero), &&operator nie wie jeszcze wyniku, a && bwięc musi biec, baby się dowiedzieć. Jeśli się bpowiedzie, to się a && bpowiedzie i ||wie, że ogólny wynik to sukces, więc nie trzeba go uruchamiać c. Jeśli to się bnie powiedzie||nadal nie zna wartości wyrażenia, więc musi zostać uruchomiony c.


7

Operator && wykonuje następne polecenie, jeśli poprzednie polecenie zakończyło się pomyślnie (zwrócony kod wyjścia ($?) 0 = logiczna prawda).

W formie A && B || Cpolecenie (lub warunek) A jest oceniane, a jeśli A zwróci wartość true (sukces, kod wyjścia 0), wówczas polecenie B zostanie wykonane. Jeśli A nie powiedzie się (w ten sposób zwróci fałsz - kod wyjścia inny niż 0) i / lub B zawiedzie ( zwróci fałsz ), wówczas polecenie C zostanie wykonane.

Również &&operator jest używany jako AND w sprawdzaniu stanu, a operator ||działa jak OR w sprawdzaniu stanu.

W zależności od tego, co chcesz zrobić ze skryptem, formularza A && B || Cmożna użyć do sprawdzania warunków, takiego jak twój przykład, lub do połączenia poleceń i zapewnienia serii poleceń do wykonania, jeśli poprzednie polecenia miały kod wyjścia 0 .
To dlatego jest często można zauważyć komendy jak:
do_something && do_something_else_that_depended_on_something.

Przykłady:
apt-get update && apt-get upgrade Jeśli aktualizacja się nie powiedzie, aktualizacja nie zostanie wykonana (ma to sens w prawdziwym świecie ...).

mkdir test && echo "Something" > test/file
Część echo "Something"zostanie wykonana tylko wtedy, gdy mkdir testzakończy się powodzeniem, a operacja zwróci kod wyjścia 0 .

./configure --prefix=/usr && make && sudo make install
Zwykle znajduje się przy kompilowaniu zadań w celu połączenia wymaganych komend zależnych.

Jeśli spróbujesz wdrożyć powyżej „łańcuchy” z czy - następnie - jeszcze trzeba będzie dużo więcej poleceń i kontroli (a więc więcej kodu napisać - więcej rzeczy się nie udać) dla prostego zadania.

Należy również pamiętać, że łańcuchy poleceń zawierają && i || są odczytywane przez powłokę od lewej do prawej. Konieczne może być grupowanie poleceń i sprawdzanie warunków za pomocą nawiasów, aby uzależnić następny krok od pomyślnego wyniku niektórych poprzednich poleceń. Na przykład zobacz to:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

Lub przykład z życia:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Należy pamiętać, że niektóre polecenia zwracają różne kody wyjścia w zależności od wykonywanego procesu lub zwracają różne kody w zależności od ich akcji (na przykład polecenie GNU diffzwraca 1, jeśli dwa pliki się różnią, a 0, jeśli nie). Takie polecenia należy traktować ostrożnie w && i || .

Również po to, aby mieć wszystkie układanki razem, należy pamiętać o łączeniu poleceń za pomocą ;operatora. W formacie A;B;Cwszystkie polecenia będą wykonywane szeregowo, bez względu na kod wyjścia polecenia Ai B.


1

Wiele zamieszania na ten temat może wynikać z tego, że dokumentacja bash wywołuje te listy AND i OR . Mimo że są logicznie podobne &&i ||znajdują się w nawiasach kwadratowych, działają inaczej.

Niektóre przykłady najlepiej ilustrują to ...

UWAGA: Pojedyncze i podwójne nawiasy kwadratowe ( [ ... ]i [[ ... ]]) są osobnymi poleceniami, które dokonują porównania i zwracają kod wyjścia. W rzeczywistości nie potrzebują if.

cmda  && cmdb  || cmdc

Jeśli cmdawyjście true, cmdbjest wykonywane.
Jeśli cmdawyjdzie false, cmdbNIE zostanie wykonane, ale cmdczostanie wykonane.

cmda; cmdb  && cmdc  || cmdd

Jak cmdawyjścia są ignorowane.
Jeśli cmdbwyjście true, cmdcjest wykonywane.
Jeśli cmdbwyjdzie false, cmdcNIE zostanie wykonane i cmddjest.

cmda  && cmdb; cmdc

Jeśli cmdawyjście true, cmdbjest wykonywane, a następnie cmdc.
Jeśli cmdawyjdzie false, cmdbNIE zostanie wykonane, ale cmdczostanie wykonane.

Co? Dlaczego jest cmdcwykonywany?
Ponieważ dla interpretatora średnik ( ;) i nowa linia oznaczają dokładnie to samo. Bash widzi ten wiersz kodu jako ...

cmda  && cmdb
cmdc  

Aby osiągnąć oczekiwane, musimy zamknąć cmdb; cmdcwewnątrz nawiasów klamrowych, aby uczynić je Poleceniem złożonym (polecenie grupy) . Dodatkowy średnik kończący jest tylko wymogiem { ...; }składni. Więc otrzymujemy ...

cmda && { cmdb; cmdc; }
Jeśli cmdawyjście true, cmdbjest wykonywane, a następnie cmdc.
Jeśli cmdawychodzi fałszywa, ani cmdbczy cmdcjest wykonywany.
Wykonanie jest kontynuowane od następnego wiersza.

Stosowanie

Listy poleceń warunkowych są najbardziej przydatne do jak najszybszego powrotu z funkcji, a tym samym do unikania interpretacji i wykonywania dużej ilości niepotrzebnego kodu. Wiele zwrotów funkcji oznacza jednak, że trzeba mieć obsesję na punkcie utrzymywania krótkich funkcji, aby łatwiej było zapewnić, że wszystkie możliwe warunki zostaną pokryte.

Oto przykład z działającego kodu ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
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.