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.