Comment représenter plusieurs conditions dans une instruction shell if?

308

Je veux représenter plusieurs conditions comme celle-ci:

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

mais quand j'exécute le script, ça montre

syntax error at line 15: `[' unexpected, 

où la ligne 15 est celle indiquant si ....

Quel est le problème avec cette condition? Je suppose que quelque chose ne va pas avec le ().

user389955
la source
6
Vous ne posez pas de questions sur les conditions du shell mais sur les conditions de test . L'expression entière dans votre exemple est évaluée par test( [) et non par le shell. Le shell évalue uniquement l'état de sortie de [.
ceving

Réponses:

381

Technique classique (métacaractères d'échappement):

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

J'ai joint les références à $gentre guillemets; c'est une bonne pratique, en général. Strictement, les parenthèses ne sont pas nécessaires car la priorité de -aet la -orend correcte même sans elles.

Notez que les opérateurs -aet -ofont partie de la spécification POSIX pour test, alias [, principalement pour la compatibilité descendante (car ils faisaient partie de la test7e édition UNIX, par exemple), mais ils sont explicitement marqués comme `` obsolètes '' par POSIX. Bash (voir les expressions conditionnelles ) semble préempter les significations classiques et POSIX pour -aet -oavec ses propres opérateurs alternatifs qui prennent des arguments.


Avec un peu de soin, vous pouvez utiliser l' [[opérateur le plus moderne , mais sachez que les versions dans Bash et Korn Shell (par exemple) n'ont pas besoin d'être identiques.

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

Exemple d'exécution, en utilisant Bash 3.2.57 sur 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

Vous n'avez pas besoin de citer les variables [[comme vous le faites [car ce n'est pas une commande distincte de la même manière [.


N'est-ce pas une question classique?

Je l'aurais pensé. Cependant, il existe une autre alternative, à savoir:

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

En effet, si vous lisez les directives du «shell portable» pour l' autoconfoutil ou les packages associés, cette notation - en utilisant « ||» et « &&» - est ce qu'ils recommandent. Je suppose que vous pourriez même aller jusqu'à:

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

Lorsque les actions sont aussi triviales que l'écho, ce n'est pas mauvais. Lorsque le bloc d'action à répéter est composé de plusieurs lignes, la répétition est trop douloureuse et l'une des versions antérieures est préférable - ou vous devez encapsuler les actions dans une fonction qui est invoquée dans les différents thenblocs.

Jonathan Leffler
la source
9
Il est bon de savoir que les parenthèses échappées fonctionnent; en aparté: dans ce cas particulier, les parenthèses ne sont même pas nécessaires, car a -aen fait une priorité plus élevée que -o(contrairement à &&et ||dans le shell - cependant, à l' intérieur des bash [[ ... ]] conditionnelles , a && également une priorité plus élevée que ||). Si vous vouliez éviter -aet -opour une robustesse et une portabilité maximales - ce que la page de manuel POSIX suggère elle-même - vous pouvez également utiliser des sousif ([ $g -eq 1 ] && [ "$c" = "123" ]) || ([ $g -eq 2 ] && [ "$c" = "456" ])
coquilles
Bonne solution, mais j'aime le formulaire sans toutes les parenthèses et supports:if test "$g" -eq 1 -a "$c" = "123" || test "$g" -eq 2 -a "$c" = "456"; then ...
Mogens TrasherDK
Je suis un peu en retard à cela, mais c'est toujours un résultat de recherche supérieur, donc je voudrais juste noter que l'utilisation de &&ou ||est également préférable car elle se comporte plus comme des conditions dans d'autres langues et vous permet de court-circuiter les conditions. Par exemple: if [ -n "${check_inodes}" ] && [ "$(stat -f %i "/foo/bar")" = "$(stat -f %i "/foo/baz")" ]. En procédant de cette façon, si check_inodesest vide, vous évitez deux appels à stat, tandis qu'une condition de test plus grande et complexe doit traiter tous les arguments avant de s'exécuter (ce qui peut également entraîner des bogues si vous oubliez ce comportement).
Haravikk
182

Dans Bash:

if [[ ( $g == 1 && $c == 123 ) || ( $g == 2 && $c == 456 ) ]]
En pause jusqu'à nouvel ordre.
la source
9
Certainement la meilleure approche bash. En aparté: dans ce cas particulier, les parenthèses ne sont même pas nécessaires, car l' intérieur des [[ ... ]] conditions a && en fait une priorité plus élevée que ||- contrairement à l' extérieur de telles conditions.
mklement0
Existe-t-il un moyen de savoir quelle condition correspond ici? Était-ce celui de la première parenthèse ou l'autre?
Firelord
1
@Firelord: Vous devez séparer les conditions dans une instruction if/ elseet avoir le code entre thenet fimettre une fonction afin d'éviter de la répéter. Dans des cas très simples, vous pouvez utiliser une caseinstruction avec ;&fallthrough (dans Bash 4).
pause jusqu'à nouvel ordre.
1
Quel est le but des deux crochets?
peterchaula
2
@peter: Veuillez voir ma réponse ici , Constructions conditionnelles dans le manuel Bash et BashFAQ / 31 .
pause jusqu'à nouvel ordre.
37

L'utilisation de /bin/bashce qui suit fonctionnera:

if [ "$option" = "Y" ] || [ "$option" = "y" ]; then
    echo "Entered $option"
fi
sunitha
la source
8

Soyez prudent si vous avez des espaces dans vos variables de chaîne et que vous vérifiez leur existence. Assurez-vous de les citer correctement.

if [ ! "${somepath}" ] || [ ! "${otherstring}" ] || [ ! "${barstring}" ] ; then
orkoden
la source
1
quand on utilise les accolades après le symbole du dollar !!
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
la source
2
C'est un sous-shell inutile. { ;}pourrait être utilisé à la place.
phk
2

En bash pour la comparaison de chaînes, vous pouvez utiliser la technique suivante.

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

Exemple:

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
la source
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
la source
3
Bienvenue dans Stack Overflow! Cette question cherche une explication , pas simplement un code de travail. Votre réponse ne donne aucun aperçu à l'interlocuteur et peut être supprimée. Veuillez modifier pour expliquer les causes des symptômes observés.
Toby Speight
0

vous pouvez également chaîner plus de 2 conditions:

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
la source