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 ().


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.


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 ) ]]

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


37

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

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

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

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

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

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

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.