Wie kann man mehrere Bedingungen in einer Shell-if-Anweisung darstellen?

308

Ich möchte mehrere Bedingungen wie diese darstellen:

if [ ( $g -eq 1 -a "$c" = "123" ) -o ( $g -eq 2 -a "$c" = "456" ) ]   
then  
    echo abc;  
else  
    echo efg;   
fi  

aber wenn ich das Skript ausführe, zeigt es

syntax error at line 15: `[' unexpected, 

wobei Zeile 15 diejenige ist, die zeigt, ob ....

Was ist falsch an dieser Bedingung? Ich denke, etwas stimmt nicht mit dem ().

user389955
quelle
6
Sie fragen nicht nach Shell-Bedingungen, sondern nach Testbedingungen . Der gesamte Ausdruck in Ihrem Beispiel wird von test( [) und nicht von der Shell ausgewertet . Die Shell wertet nur den Exit-Status von aus [.
Ceving

Antworten:

381

Klassische Technik (Escape-Metazeichen):

if [ \( "$g" -eq 1 -a "$c" = "123" \) -o \( "$g" -eq 2 -a "$c" = "456" \) ]
then echo abc
else echo efg
fi

Ich habe die Verweise auf $gin doppelte Anführungszeichen gesetzt; Das ist im Allgemeinen eine gute Praxis. Streng genommen werden die Klammern nicht benötigt, da sie Vorrang haben -aund -oauch ohne sie korrekt sind.

Beachten Sie, dass die Operatoren -aund -oTeil der POSIX-Spezifikation testsind [, vor allem aus Gründen der Abwärtskompatibilität (da sie beispielsweise Teil der test7. Edition von UNIX waren), von POSIX jedoch ausdrücklich als "veraltet" gekennzeichnet werden. Bash (siehe bedingte Ausdrücke ) scheint die klassischen und POSIX-Bedeutungen für -aund -omit eigenen alternativen Operatoren, die Argumente annehmen, zu verhindern.


Mit einiger Sorgfalt können Sie den moderneren [[Operator verwenden. Beachten Sie jedoch, dass die Versionen in Bash und Korn Shell (zum Beispiel) nicht identisch sein müssen.

for g in 1 2 3
do
    for c in 123 456 789
    do
        if [[ ( "$g" -eq 1 && "$c" = "123" ) || ( "$g" -eq 2 && "$c" = "456" ) ]]
        then echo "g = $g; c = $c; true"
        else echo "g = $g; c = $c; false"
        fi
    done
done

Beispiellauf mit Bash 3.2.57 unter Mac OS X:

g = 1; c = 123; true
g = 1; c = 456; false
g = 1; c = 789; false
g = 2; c = 123; false
g = 2; c = 456; true
g = 2; c = 789; false
g = 3; c = 123; false
g = 3; c = 456; false
g = 3; c = 789; false

Sie brauchen nicht die Variablen in zitieren , [[wie Sie mit zu tun , [weil es nicht ein separater Befehl in der gleichen Art und Weise, die [ist.


Ist das nicht eine klassische Frage?

Das hätte ich mir gedacht. Es gibt jedoch eine andere Alternative, nämlich:

if [ "$g" -eq 1 -a "$c" = "123" ] || [ "$g" -eq 2 -a "$c" = "456" ]
then echo abc
else echo efg
fi

Wenn Sie die Richtlinien für tragbare Shell für das autoconfTool oder verwandte Pakete lesen , wird diese Notation - mit ' ||' und ' &&' - empfohlen. Ich nehme an, Sie könnten sogar so weit gehen:

if [ "$g" -eq 1 ] && [ "$c" = "123" ]
then echo abc
elif [ "$g" -eq 2 ] && [ "$c" = "456" ]
then echo abc
else echo efg
fi

Wo die Aktionen so trivial sind wie Echo, ist das nicht schlecht. Wenn der zu wiederholende Aktionsblock aus mehreren Zeilen besteht, ist die Wiederholung zu schmerzhaft und eine der früheren Versionen ist vorzuziehen - oder Sie müssen die Aktionen in eine Funktion einschließen, die in den verschiedenen thenBlöcken aufgerufen wird .

Jonathan Leffler
quelle
9
Es ist gut zu wissen, dass entkommene Klammern funktionieren. Nebenbei bemerkt: In diesem speziellen Fall werden die Klammern nicht einmal benötigt, da sie -atatsächlich eine höhere Priorität haben als -o(im Gegensatz zu &&und ||in der Shell - innerhalb von bash [[ ... ]] Bedingungen hat sie jedoch && auch eine höhere Priorität als ||). Wenn Sie vermeiden -aund -ofür maximale Robustheit und Portabilität - was die POSIX-Manpage selbst vorschlägt - möchten, können Sie auch Subshells zum Gruppieren verwenden:if ([ $g -eq 1 ] && [ "$c" = "123" ]) || ([ $g -eq 2 ] && [ "$c" = "456" ])
mklement0
Gute Lösung, aber ich mag die Form ohne alle Klammern und Klammern:if test "$g" -eq 1 -a "$c" = "123" || test "$g" -eq 2 -a "$c" = "456"; then ...
Mogens TrasherDK
Ich bin etwas spät dran, aber es ist immer noch ein Top-Suchergebnis. Daher möchte ich nur darauf hinweisen, dass die Verwendung von &&oder ||auch vorzuziehen ist, da es sich eher wie Bedingungen in anderen Sprachen verhält und Sie Kurzschlussbedingungen ermöglicht. Zum Beispiel : if [ -n "${check_inodes}" ] && [ "$(stat -f %i "/foo/bar")" = "$(stat -f %i "/foo/baz")" ]. Wenn Sie dies auf diese Weise tun check_inodes, vermeiden Sie zwei Aufrufe von stat, während eine größere, komplexe Testbedingung vor der Ausführung alle Argumente verarbeiten muss (was auch zu Fehlern führen kann, wenn Sie dieses Verhalten vergessen).
Haravikk
182

In Bash:

if [[ ( $g == 1 && $c == 123 ) || ( $g == 2 && $c == 456 ) ]]
Bis auf weiteres angehalten.
quelle
9
Auf jeden Fall der beste Ansatz in bash. Nebenbei: In diesem speziellen Fall werden die Klammern nicht einmal benötigt, da innere [[ ... ]] Bedingungen && tatsächlich eine höhere Priorität haben als ||- im Gegensatz zu solchen äußeren Bedingungen.
mklement0
Gibt es eine Möglichkeit herauszufinden, welche Bedingung hier übereinstimmt? War es der in der ersten Klammer oder der andere?
Feuerlord
1
@Firelord: Sie müssten die Bedingungen in eine if/ -Anweisung trennen elseund den Code zwischen thenund fieine Funktion einfügen , um eine Wiederholung zu vermeiden. In sehr einfachen Fällen können Sie eine caseAnweisung mit ;&Fallthrough verwenden (in Bash 4).
Bis auf weiteres angehalten.
1
Was ist der Zweck der beiden eckigen Klammern?
Peterchaula
2
@ Peter: Bitte sehen Sie meine Antwort hier , Bedingte Konstrukte im Bash-Handbuch und BashFAQ / 31 .
Bis auf weiteres angehalten.
37

Die Verwendung /bin/bashder folgenden Funktionen funktioniert:

if [ "$option" = "Y" ] || [ "$option" = "y" ]; then
    echo "Entered $option"
fi
Sunitha
quelle
8

Seien Sie vorsichtig, wenn Ihre Zeichenfolgenvariablen Leerzeichen enthalten und Sie auf Existenz prüfen. Stellen Sie sicher, dass Sie sie richtig zitieren.

if [ ! "${somepath}" ] || [ ! "${otherstring}" ] || [ ! "${barstring}" ] ; then
Orkoden
quelle
1
wenn wir die geschweiften Klammern nach dem Dollarsymbol verwenden !!
YouAreAwesome
6
$ g=3
$ c=133
$ ([ "$g$c" = "1123" ] || [ "$g$c" = "2456" ]) && echo "abc" || echo "efg"
efg
$ g=1
$ c=123
$ ([ "$g$c" = "1123" ] || [ "$g$c" = "2456" ]) && echo "abc" || echo "efg"
abc
Ghostdog74
quelle
2
Das ist eine nutzlose Unterschale. { ;}könnte stattdessen verwendet werden.
Phk
2

In Bash für den Zeichenfolgenvergleich können Sie die folgende Technik verwenden.

if [ $var OP "val" ]; then
    echo "statements"
fi

Beispiel:

var="something"
if [ $var != "otherthing" ] && [ $var != "everything" ] && [ $var != "allthings" ]; then
    echo "this will be printed"
else
    echo "this will not be printed"
fi
Ashan Priyadarshana
quelle
0
#!/bin/bash

current_usage=$( df -h | grep 'gfsvg-gfslv' | awk {'print $5'} )
echo $current_usage
critical_usage=6%
warning_usage=3%

if [[ ${current_usage%?} -lt ${warning_usage%?} ]]; then
echo OK current usage is $current_usage
elif [[ ${current_usage%?} -ge ${warning_usage%?} ]] && [[ ${current_usage%?} -lt ${critical_usage%?} ]]; then
echo Warning $current_usage
else
echo Critical $current_usage
fi
Debjyoti Banerjee
quelle
3
Willkommen bei Stack Overflow! Diese Frage sucht nach einer Erklärung , nicht nur nach Arbeitscode. Ihre Antwort bietet dem Fragesteller keinen Einblick und kann gelöscht werden. Bitte bearbeiten Sie, um zu erklären, was die beobachteten Symptome verursacht.
Toby Speight
0

Sie können auch mehr als 2 Bedingungen verketten:

if [ \( "$1" = '--usage' \) -o \( "$1" = '' \) -o \( "$1" = '--help' \) ]
then
   printf "\033[2J";printf "\033[0;0H"
   cat << EOF_PRINT_USAGE

   $0 - Purpose: upsert qto http json data to postgres db

   USAGE EXAMPLE:

   $0 -a foo -a bar



EOF_PRINT_USAGE
   exit 1
fi
Yordan Georgiev
quelle