Unterschied zwischen [[Ausdruck1 || Ausdruck2]] und [[Ausdruck1]] || [[Ausdruck2]]

7

Betrachten Sie zwei bedingte Ausdrücke expr1und expr2zum Beispiel $i -eq $jund $k -eq $l. Wir können dies auf verschiedene bashArten schreiben . Hier sind zwei Möglichkeiten

[[ expr1 || expr2 ]]

[[ expr1 ]] || [[ expr2 ]]

Ich bin mir ziemlich sicher, dass ich hier Empfehlungen gesehen habe, dass die zweite bevorzugt werden sollte, aber ich kann keine Beweise finden, die dies unterstützen.

Hier ist ein Beispielskript, das zu demonstrieren scheint, dass es keinen Unterschied gibt:

for i in 0 1
do
  for j in 0 1
  do
    for k in 0 1
    do
      for l in 0 1
      do
        if [[ $i -eq $j || $k -eq $l ]]; then printf "1-yes\t"; else printf "1-no\t"; fi
        if [[ $i -eq $j ]] || [[ $k -eq $l ]]; then printf "2-yes\n"; else printf "2-no\n"; fi
      done
    done
  done
done

und Ausgabe, die zeigt, dass beide Bedingungskonstrukte das gleiche Ergebnis erzeugen:

1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes

Gibt es einen Vorteil bei der Verwendung eines Konstrukts gegenüber dem anderen?

Für Bonuspunkte dieselbe Frage, aber Verallgemeinerung auf mehrere Bedingungen mit ||und &&. Zum Beispiel [[ expr1 && expr2 || expr3 ]].

Roaima
quelle
In Bezug darauf, warum Empfehlungen dieser Art häufig geschrieben werden. [oder test( nicht [[ ) suchen Sie nach den OBTags (verknüpft mit der "veralteten" Definition) in der POSIX-Spezifikation fürtest . In testeinigen pathologischen Fällen kann es unmöglich sein zu sagen, ob (oder )soll eine Syntax sein, die für den Testbefehl oder eine zu testende Zeichenfolge von Bedeutung ist. Daher können Personen, die die Veralterungsmarkierungen ignorieren und diese Syntax verwenden, tatsächlich das Gegenteil benötigen. veraltete "x$foo"Praxis; mit [[, das ist kein problem.
Charles Duffy

Antworten:

7

Ich denke, die Empfehlung, die Sie gesehen haben, war für POSIX sh und / oder den testBefehl, der gleichzeitig als [Befehl fungiert, und nicht für das [[Konstrukt, das in ksh erschien (danke Stéphane Chazelas für den Tipp) und das auch zum Beispiel in bash, zsh und einigen anderen verwendet wird Muscheln.

In den meisten Sprachen wie C müssen die verbleibenden Teile je nach Operation nicht ausgewertet werden, wenn eine Klausel bereits als wahr oder falsch bekannt ist: Wenn wahr, nicht nach einer logischen oder nach ihr, wenn falsch, nicht nach einer logischen und , usw. Dies ermöglicht es beispielsweise, anzuhalten, wenn ein Zeiger NULL ist, und nicht zu versuchen, ihn in der nächsten Klausel zu dereferenzieren.

Aber sh ‚s - [ expr1 -o expr2 ]Konstrukt (einschließlich bash-Implementierung) tut dies nicht: Es wertet immer beiden Seiten, wenn man wollen würde nur ausdr1 ausgewertet werden. Dies wurde möglicherweise aus Gründen der Kompatibilität mit der testBefehlsimplementierung durchgeführt. Auf der anderen Seite folgen shs ||und &&do dem üblichen Prinzip: nicht bewertet, wenn es das Ergebnis nicht ändert.

Der zu beachtende Unterschied wäre also:

: > /tmp/effect #clear effect
if [ 1 -eq 1 -o $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

was ergibt:

true
or-was-evaluated

Oben [könnte jeder durch /usr/bin/[einen Alias ​​für den testBefehl ersetzt worden sein, der zuvor [in Shells eingebaut wurde.

Während die beiden nächsten Konstrukte:

: > /tmp/effect #clear effect
if [ 1 -eq 1 ] || [ $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

oder

: > /tmp/effect #clear effect
if [[ 1 -eq 1 || $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]]; then
    echo true;
fi
cat /tmp/effect

Wird nur nachgeben trueund effectleer lassen: ||verhält sich korrekt und hat auch [[dieses Problem behoben.


AKTUALISIEREN:

Wie @ StéphaneChazelas kommentierte, habe ich einige Unterschiede in Bezug auf die ursprüngliche Frage übersehen. Ich werde hier nur das Wichtigste (zumindest für mich) nennen: die Priorität der Betreiber.

Während die Shell keinen Vorrang hat:

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

Ausbeuten (weil es keinen Vorrang gibt und somit zuerst true || trueausgewertet wird und dann && false):

false

innerhalb [[ ]]des &&Operators hat Vorrang vor ||:

if [[ 1 -eq 1 || 1 -eq 1 && 1 -eq 0 ]]; then
    echo true
else
    echo false
fi

ergibt (weil 1 -eq 1 && 1 -eq 0gruppiert ist und somit das 2. Mitglied von ist ||):

true

Zumindest für ksh , bash , zsh .

Das [[ ]]Verhalten gegenüber [ ]logischen Shell-Operatoren und direkten Shell-Operatoren wurde verbessert .

AB
quelle
1
[ x -o y ]muss nicht beide Seiten bewerten. Während GNU testoder das [eingebaute von bashdo, würden Sie strace zsh -c '[ x -o -f /x ]'damit [nicht versuchen, stat () /x. Gleiches gilt für mksh. Aber es ist wahr, dass -ound -aschwer beschädigt, nicht zuverlässig verwendet werden können und von POSIX veraltet sind.
Stéphane Chazelas
1
Ein Unterschied besteht darin , dass im Inneren [[...]], &&hat eine höhere Priorität als ||, während außerhalb sie gleich Vorrang haben. Vergleiche ksh -c '[[ x || x && "" ]]'vssh -c 'true || true && false'
Stéphane Chazelas
1
Beachten Sie, dass der Standard [/ das testDienstprogramm keinen ==Operator hat. Der Gleichheitsoperator ist =(beachten Sie, dass innerhalb KSH ist [[...]], =/ ==sind nicht Gleichheitsoperatoren, sondern Musterabgleich sind).
Stéphane Chazelas
1
Es ist umgekehrt. &&hat Vorrang vor ||innen [[...]](oder ((...))) , aber die nicht &&und ||Shell - Operatoren. [[ x || y && z ]]is x || (y && z)while x || y && zis (x || y) && z(Operatoren werden von links nach rechts ohne Vorrang ausgewertet). Ähnlich wie *hat Vorrang vor +( 1 + 2 * 3ist 1 + (2 * 3)), aber +und -haben den gleichen Vorrang.
Stéphane Chazelas
1
Wenn &&Vorrang vor hat ||, sollte es true || (true && false)statt(true || true) && false
Prvt_Yadav