¿Hay un operador "en" en bash / bourne?

17

Estoy buscando un operador "in" que funcione de esta manera:

if [ "$1" in ("cat","dog","mouse") ]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

Obviamente, es una declaración mucho más corta en comparación con, por ejemplo, el uso de varias pruebas "o".

mrjayviper
fuente

Respuestas:

31

Puedes usar case...esac

$ cat in.sh 
#!/bin/bash

case "$1" in 
  "cat"|"dog"|"mouse")
    echo "dollar 1 is either a cat or a dog or a mouse"
  ;;
  *)
    echo "none of the above"
  ;;
esac

Ex.

$ ./in.sh dog
dollar 1 is either a cat or a dog or a mouse
$ ./in.sh hamster
none of the above

Con ksh, bash -O extglobo zsh -o kshglob, también podría usar un patrón de globo extendido:

if [[ "$1" = @(cat|dog|mouse) ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi

Con bash, ksh93o zsh, también podría usar una comparación de expresión regular:

if [[ "$1" =~ ^(cat|dog|mouse)$ ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi
conductor de acero
fuente
alguna razón para los corchetes dobles? ¡Gracias de nuevo!
mrjayviper
1
@mrjayviper, los corchetes dobles son una construcción de prueba extendida: AFAIK, el operador de expresiones regulares =~no es válido dentro de la prueba POSIX de un solo corchete
steeldriver
1
@steeldriver Sólo [es POSIX, pero [[es característica extendida de bash, ksh (al parecer se originó a partir de ahí, y zsh. caseejemplo es más POSIX de todo, sin embargo
Sergiy Kolodyazhnyy
2
@ StéphaneChazelas Dado que al menos bash 4.1-alpha no es necesario establecer extglog explícitamente. De los cambios de bash : s. Forzar extglob temporalmente al analizar el argumento del patrón a los operadores == y! = Al comando [[comando, por compatibilidad.
Isaac
@steeldriver case "$1" inNo es necesario citar $ 1 en , no se realizan divisiones de palabras ni expansión de nombre de ruta en ese token.
Isaac
9

No hay una prueba "in" en bash, pero hay una prueba regex (no en bourne):

if [[ $1 =~ ^(cat|dog|mouse)$ ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

Y generalmente escrito usando una variable (menos problemas con las citas):

regex='^(cat|dog|mouse)$'

if [[ $1 =~ $regex ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

Para un shell Bourne anterior, debe usar una coincidencia de mayúsculas y minúsculas:

case $1 in
    cat|dog|mouse)   echo "dollar 1 is either a cat or a dog or a mouse";;
esac
Isaac
fuente
Esta es la respuesta aceptada para mí.
Nam G VU
6

Usar a caseestá bien y está bien si tienes un conjunto fijo de mascotas con las que quieres hacer coincidir. Pero no funcionará si necesita construir el patrón en tiempo de ejecución, ya caseque no interpreta la alternancia desde dentro de los parámetros expandidos.

Esto solo coincidirá con la cadena literal cat|dog|mouse:

patt='cat|dog|mouse'
case $1 in 
        $patt) echo "$1 matches the case" ;; 
esac

Sin embargo, puede usar una variable con la coincidencia de expresión regular. Mientras no se cite la variable, cualquier operador de expresiones regulares dentro de ella tiene sus significados especiales.

patt='cat|dog|mouse'
if [[ "$1" =~ ^($patt)$ ]]; then
        echo "$1 matches the pattern"
fi

También podría usar matrices asociativas. Verificar si existe una clave en uno es lo más cercano a un inoperador que Bash le da. Aunque la sintaxis es un poco fea:

declare -A arr
arr[cat]=1
arr[dog]=1
arr[mouse]=1

if [ "${arr[$1]+x}" ]; then
        echo "$1 is in the array"
fi

(se ${arr[$1]+x}expande a xsi arr[$1]está configurado, de lo contrario se vacía ) .

ilkkachu
fuente
5

Usted podría utilizar una casedeclaración en una ifprueba, pero el código se vería un poco peluda:

if case "$1" in (cat|dog|mouse) true ;; (*) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

o un poco más corto

if ! case "$1" in (cat|dog|mouse) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

Esto está usando una casecláusula solo para hacer la coincidencia de patrón para la ifcláusula. Introduce una prueba verdadera / falsa innecesaria.

Es mejor simplemente usar case:

case "$1" in
    cat|dog|mouse)
        printf '"%s" is one of cat, dog or mouse\n' "$1"
        ;;
    *)
        printf '"%s" is unknown\n' "$1"
esac

No hagas esto:

is_one_of () {
    eval "case $1 in ($2) return 0; esac"
    return 1
}

if is_one_of "$1" 'cat|dog|mouse'; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

o esto:

is_one_of () (
    word=$1
    shift
    IFS='|'
    eval "case $word in ($*) return 0; esac"
    return 1
)

if is_one_of "$1" cat dog mouse; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

... porque solo está agregando información más peligrosa, solo para poder usar una ifdeclaración en su código en lugar de una casedeclaración perfectamente razonable .

Kusalananda
fuente
¿No sería mejor dividir mayúsculas y minúsculas en llamadas a funciones y evaluar el estado de salida dentro de la instrucción if?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy ¿Y dejar que el patrón sea un argumento para la función? Tendría que hacer una evalde las casedeclaraciones en ese caso, y sería aún más propenso a errores.
Kusalananda
En este caso hay un patrón simple, cada uno podría pasarse como parámetro posicional para funcionar y funcionar "$1"|"$2"|"$3". También unix.stackexchange.com/a/234415/85039 Pero sí, es un poco peludo.
Sergiy Kolodyazhnyy
4

grep Acercarse.

if echo $1 | grep -qE "^(cat|dog|mouse)$"; then 
    echo "dollar 1 is either a cat or a dog or a mouse"
fi
  • -qpara evitar cualquier salida a la pantalla (más rápido para escribir que >/dev/null).
  • -Epara (cat|dog|mouse)aspectos de expresiones regulares extendidas necesita esto.
  • ^(cat|dog|mouse)$coincide con cualquier línea que comience ( ^) con gato, perro o mouse ( (cat|dog|mouse)) seguido del final de la línea ( $)
Steve
fuente