Sind if else-Anweisungen gleichbedeutend mit logisch und && oder || und wo soll ich eins dem anderen vorziehen?

27

Ich lerne über Entscheidungsstrukturen und bin auf folgende Codes gestoßen:

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


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

Beide verhalten sich gleich. Gibt es irgendwelche Vorteile, wenn man den einen Weg vom anderen benutzt?

Subhaa Chandar
quelle
3
Sie sind nicht gleichwertig. Siehe die ausgezeichnete Antwort von Ikarus. Angenommen, ./myfile existiert, ist aber nicht lesbar.
AlexP

Antworten:

26

Nein, Konstruktionen if A; then B; else C; fiund A && B || Csind nicht gleichwertig .

Mit if A; then B; else C; fiwird der Befehl Aimmer ausgewertet und ausgeführt (es wird zumindest versucht, ihn auszuführen) und dann werden entweder der Befehl Boder der Befehl Causgewertet und ausgeführt.

Mit A && B || Cist es für Befehle gleich Aund Bfür C: Befehl Cwird ausgewertet und ausgeführt, wenn entweder A fehlschlägt oder B fehlschlägt.

Nehmen wir in Ihrem Beispiel an , Sie werden es chmod u-r ./myfiletrotz [ -f ./myfile ]Erfolg tuncat /home/user/myfile

Mein Rat: Verwenden Sie A && Boder A || Balles, was Sie wollen, dies bleibt leicht zu lesen und zu verstehen und es gibt keine Falle. Aber wenn du meinst, wenn ... dann ... sonst ... dann benutze if A; then B; else C; fi.

xhienne
quelle
29

Die meisten Menschen finden es einfacher , das zu verstehen if... then... else... fiForm.

Für die a && b || cmuss man sicher sein, dass btrue zurückgegeben wird. Dies ist eine Ursache für subtile Fehler und ein guter Grund, diesen Stil zu vermeiden. Wenn b nicht true zurückgibt, sind diese nicht gleich.

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

Für sehr kurze Tests und Aktionen, die keine else-Klausel enthalten, ist die verkürzte Länge attraktiv, z

 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

&&und ||werden short circuiting operators, sobald das Ergebnis bekannt ist, weitere nicht benötigte Tests übersprungen. a && b || cist gruppiert als (a && b) || c. Zuerst awird ausgeführt. Wenn es failsso definiert ist, dass es keinen Exit-Status von 0 zurückgibt, ist die Gruppe (a && b)bekannt failund bmuss nicht ausgeführt werden. Der ||kennt das Ergebnis des Ausdrucks nicht, muss also ausgeführt werden c. Wenn dies aerfolgreich ist (Null zurückgibt), &&weiß der Bediener das Ergebnis noch nicht und a && bmuss es erst ausführen b, um es herauszufinden. Wenn dies bgelingt, ist dies a && berfolgreich und das ||Gesamtergebnis lautet "Erfolg". Sie müssen nicht ausgeführt werden c. Wenn bdann fehlschlägt||kennt den Wert des Ausdrucks noch nicht, muss also ausgeführt werden c.

icarus
quelle
7

Der Operator && führt den nächsten Befehl aus, wenn der vorherige Befehl erfolgreich ausgeführt wurde (zurückgegebener Exit-Code ($?) 0 = logisch wahr).

In Form A && B || Cwird der Befehl (oder die Bedingung) A ausgewertet, und wenn A true zurückgibt (Erfolg, Beendigungscode 0), wird der Befehl B ausgeführt. Wenn A fehlschlägt (so kehrt falsche - Beendigungscode ungleich 0) und / oder B fehlschlägt (Rückkehr falsch ) , dann Befehl C wird ausgeführt.

Der &&Operator wird auch als UND in Bedingungsprüfungen verwendet, und der Operator ||arbeitet wie ODER in Bedingungsprüfungen.

Abhängig davon, was Sie mit Ihrem Skript tun möchten, A && B || Ckann form für Bedingungsprüfungen wie in Ihrem Beispiel verwendet werden oder zum Verketten von Befehlen und zum Ausführen einer Reihe von Befehlen, wenn vorherige Befehle den erfolgreichen Beendigungscode 0 hatten .
Aus diesem Grunde ist es üblich , Befehle zu sehen , wie:
do_something && do_something_else_that_depended_on_something.

Beispiele:
apt-get update && apt-get upgrade Wenn das Update fehlschlägt, wird das Upgrade nicht ausgeführt (in der Praxis sinnvoll ...).

mkdir test && echo "Something" > test/file
Der Teil echo "Something"wird nur ausgeführt, wenn der mkdir testVorgang erfolgreich war und der Beendigungscode 0 zurückgegeben wurde .

./configure --prefix=/usr && make && sudo make install
Wird normalerweise beim Kompilieren von Jobs zum Verketten der erforderlichen abhängigen Befehle verwendet.

Wenn Sie versuchen , über „Ketten“ mit zu implementieren , wenn - dann - sonst müssen Sie viel mehr Befehle und überprüft (und damit mehr Code zu schreiben - mehr Dinge schief gehen) für eine einfache Aufgabe.

Beachten Sie auch, dass Befehle mit && und || verkettet sind werden von Shell von links nach rechts gelesen. Möglicherweise müssen Sie Befehle und Bedingungsprüfungen in Klammern gruppieren, um den nächsten Schritt von der erfolgreichen Ausgabe einiger vorheriger Befehle abhängig zu machen. Zum Beispiel siehe dies:

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

Oder ein reales Beispiel:

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

Beachten Sie, dass einige Befehle verschiedenen Exit - Codes in Abhängigkeit von dem Prozess ausgeführt zurückzukehren, oder kehren verschiedene Codes in Abhängigkeit von ihren Aktionen (zum Beispiel Befehl GNU diff, kehren 1 , wenn zwei Dateien unterscheiden, und 0 , wenn sie es nicht tun). Solche Befehle müssen in && und || mit Sorgfalt behandelt werden .

Beachten Sie auch die Verkettung von Befehlen mithilfe des ;Operators , um alle Rätsel zusammen zu haben . Bei einem Format werden A;B;Calle Befehle unabhängig vom Exit-Code von command Aund in Serie ausgeführt B.

George Vasiliou
quelle
1

Ein Großteil der Verwirrung darüber kann auf die Bash-Dokumentation zurückzuführen sein, die diese UND- und ODER-Listen aufruft . Während logisch ähnlich dem &&und ||in eckigen Klammern gefunden, funktionieren sie anders.

Einige Beispiele mögen dies am besten veranschaulichen ...

HINWEIS: Einzelne und doppelte eckige Klammern ( [ ... ]und [[ ... ]]) sind eigenständige Befehle, die einen Vergleich durchführen und einen Beendigungscode zurückgeben. Die brauchen sie eigentlich nicht if.

cmda  && cmdb  || cmdc

Wenn cmdaexit true ist, cmdbwird ausgeführt.
Wenn cmdaexit falsch ist, cmdbwird NICHT ausgeführt, ist aber cmdc.

cmda; cmdb  && cmdc  || cmdd

Wie cmdaAusgänge wird ignoriert.
Wenn cmdbexit true ist, cmdcwird ausgeführt.
Wenn cmdbexit falsch ist, cmdcwird NICHT ausgeführt und cmddist.

cmda  && cmdb; cmdc

Wenn cmdaexit true ist, cmdbwird ausgeführt, gefolgt von cmdc.
Wenn cmdaexit falsch ist, cmdbwird es NICHT ausgeführt , cmdcist es aber .

Huh? Warum wird cmdcausgeführt?
Für den Interpreten ;bedeuten ein Semikolon ( ) und ein Zeilenumbruch genau dasselbe. Bash sieht diese Codezeile als ...

cmda  && cmdb
cmdc  

Um das zu erreichen, was erwartet wird, müssen wir cmdb; cmdcgeschweifte Klammern einschließen , um sie zu einem zusammengesetzten Befehl (Gruppenbefehl) zu machen . Das zusätzliche abschließende Semikolon ist nur eine Anforderung der { ...; }Syntax. Also bekommen wir ...

cmda && { cmdb; cmdc; }
Wenn cmdaexit true ist, cmdbwird ausgeführt, gefolgt von cmdc.
Wenn cmdafalse beendet wird, wird weder cmdbnoch cmdcausgeführt.
Die Ausführung wird mit der nächsten Zeile fortgesetzt.

Verwendung

Bedingte Befehlslisten sind am nützlichsten, um Funktionen so schnell wie möglich zu verlassen und so zu vermeiden, dass unnötiger Code interpretiert und ausgeführt wird. Mehrfache Funktionsrückgaben bedeuten jedoch, dass man die Funktionen unbedingt kurz halten muss, damit leichter sichergestellt werden kann, dass alle möglichen Bedingungen erfüllt sind.

Hier ist ein Beispiel aus einem laufenden Code ...

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
}
DocSalvager
quelle