¿Cómo representar múltiples condiciones en una instrucción if de shell?

308

Quiero representar múltiples condiciones como esta:

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

pero cuando ejecuto el script, muestra

syntax error at line 15: `[' unexpected, 

donde la línea 15 es la que muestra si ...

¿Qué hay de malo en esta condición? Supongo que algo está mal con el ().

usuario389955
fuente
66
No está preguntando acerca de las condiciones de shell sino de las condiciones de prueba . La expresión completa en su ejemplo es evaluada por test( [) y no por el shell. El shell evalúa solo el estado de salida de [.
ceving

Respuestas:

381

Técnica clásica (metacaracteres de escape):

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

He incluido las referencias $gentre comillas dobles; Esa es una buena práctica, en general. Estrictamente, los paréntesis no son necesarios porque la precedencia de -ay lo -ohace correcto incluso sin ellos.

Tenga en cuenta que los operadores -ay -oson parte de la especificación POSIX para test, alias [, principalmente para la compatibilidad con versiones anteriores (ya que formaban parte de la test7a edición de UNIX, por ejemplo), pero POSIX los marca explícitamente como 'obsoletos'. Bash (ver expresiones condicionales ) parece evitar los significados clásicos y POSIX para -ay -ocon sus propios operadores alternativos que toman argumentos.


Con un poco de cuidado, puede utilizar el [[operador más moderno , pero tenga en cuenta que las versiones en Bash y Korn Shell (por ejemplo) no necesitan ser idénticas.

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

Ejemplo de ejecución, utilizando Bash 3.2.57 en 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

No necesita citar las variables [[como lo hace [porque no es un comando separado de la misma manera que lo [es.


¿No es una pregunta clásica?

Lo hubiera pensado así. Sin embargo, hay otra alternativa, a saber:

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

De hecho, si lee las pautas de 'shell portátil' para la autoconfherramienta o los paquetes relacionados, esta notación, usando ' ||' y ' &&', es lo que recomiendan. Supongo que incluso podrías ir tan lejos como:

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

Donde las acciones son tan triviales como hacer eco, esto no es malo. Cuando el bloque de acción que se repetirá es de varias líneas, la repetición es demasiado dolorosa y es preferible una de las versiones anteriores, o debe ajustar las acciones en una función que se invoca en los diferentes thenbloques.

Jonathan Leffler
fuente
99
Es bueno saber que los paréntesis escapados funcionan; como un aparte: en este caso particular, los paréntesis ni siquiera son necesarios, porque en -arealidad tiene mayor prioridad que -o(a diferencia &&y ||en el shell, sin embargo, dentro de los bash [[ ... ]] condicionales , && también tiene mayor prioridad que ||). Si desea evitar -ay -olograr la máxima robustez y portabilidad, que sugiere la página de manual POSIX, también puede usar subcapas para agrupar:if ([ $g -eq 1 ] && [ "$c" = "123" ]) || ([ $g -eq 2 ] && [ "$c" = "456" ])
mklement0
Buena solución, pero me gusta la forma sin todos los paréntesis y corchetes:if test "$g" -eq 1 -a "$c" = "123" || test "$g" -eq 2 -a "$c" = "456"; then ...
Mogens TrasherDK
Llego un poco tarde a esto, pero sigue siendo un resultado de búsqueda superior, por lo que me gustaría señalar que usar &&o ||también es preferible, ya que se comporta más como condicionales en otros idiomas y le permite cortocircuitar las condiciones. Por ejemplo: if [ -n "${check_inodes}" ] && [ "$(stat -f %i "/foo/bar")" = "$(stat -f %i "/foo/baz")" ]. Al hacerlo de esta manera, si check_inodesestá vacío, evita dos llamadas a stat, mientras que una condición de prueba más grande y compleja debe procesar todos los argumentos antes de ejecutarla (lo que también puede generar errores si olvida ese comportamiento).
Haravikk
182

En Bash:

if [[ ( $g == 1 && $c == 123 ) || ( $g == 2 && $c == 456 ) ]]
Pausado hasta nuevo aviso.
fuente
99
Definitivamente el mejor enfoque en bash. Como comentario aparte: en este caso particular, los paréntesis ni siquiera son necesarios, porque los condicionales internos en realidad tienen mayor prioridad que , a diferencia de los condicionales externos . [[ ... ]] &&||
mklement0
¿Hay alguna manera de saber qué condición coincide aquí? ¿Fue el primero entre paréntesis o el otro?
Señor del
1
@Firelord: Tendría que separar las condiciones en una declaración if/ elsey tener el código entre theny fiponer una función para evitar repetirlo. En casos muy simples, podría usar una casedeclaración con ;&fallthrough (en Bash 4).
Pausado hasta nuevo aviso.
1
¿Cuál es el propósito de los dos corchetes?
peterchaula
2
@peter: Vea mi respuesta aquí , Construcciones condicionales en el Manual de Bash y BashFAQ / 31 .
Pausado hasta nuevo aviso.
37

Usar /bin/bashlo siguiente funcionará:

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

Tenga cuidado si tiene espacios en sus variables de cadena y verifica su existencia. Asegúrese de citarlos correctamente.

if [ ! "${somepath}" ] || [ ! "${otherstring}" ] || [ ! "${barstring}" ] ; then
orkoden
fuente
1
cuando usamos las llaves después del símbolo del dólar !!
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
fuente
2
Esa es una subshell inútil. { ;}podría usarse en su lugar.
phk
2

En bash para la comparación de cadenas, puede usar la siguiente técnica.

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

Ejemplo:

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
fuente
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
fuente
3
¡Bienvenido a Stack Overflow! Esta pregunta busca una explicación , no simplemente un código de trabajo. Su respuesta no proporciona información para el interlocutor, y puede ser eliminada. Por favor edición para explicar lo que causa los síntomas observados.
Toby Speight
0

También puede encadenar más de 2 condiciones:

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
fuente