¿Cómo puedo usar una variable como condición de caso?

17

Estoy tratando de utilizar una variable que consiste en diferentes cadenas separadas con una |como una caseprueba de declaración. Por ejemplo:

string="\"foo\"|\"bar\""
read choice
case $choice in
    $string)
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

Quiero poder escribir fooo barejecutar la primera parte de la casedeclaración. Sin embargo, ambos fooy barllévame al segundo:

$ foo.sh
foo
Bad choice!
$ foo.sh
bar
Bad choice!

Usar en "$string"lugar de $stringno hace ninguna diferencia. Tampoco el uso string="foo|bar".

Sé que puedo hacerlo de esta manera:

case $choice in
    "foo"|"bar")
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

Puedo pensar en varias soluciones, pero me gustaría saber si es posible usar una variable como casecondición en bash. ¿Es posible y, de ser así, cómo?

terdon
fuente
2
No puedo sugerirlo como una respuesta real, pero dado que nadie más lo ha mencionado, podría envolver la declaración del caso en una evalopción de $ escapándose, los parens, el asterisco, el punto y coma y las nuevas líneas. Feo, pero "funciona".
Jeff Schaller
@JeffSchaller: no es una mala idea muchas veces, y tal vez sea solo el boleto en este caso. También consideré recomendarlo, pero la readparte me detuvo. en mi opinión, la validación de entrada del usuario, que es lo que parece ser, los casepatrones no deberían estar en la parte superior de la lista de evaluación, sino que deberían reducirse al *patrón predeterminado de modo que los únicos resultados que lleguen allí sean garantizados aceptables. aún así, debido a que el problema es el orden de análisis / expansión, entonces una segunda evaluación podría ser lo que se requiere.
mikeserv

Respuestas:

13

El manual de bash dice:

palabra del caso en la lista [[(] patrón [| patrón] ...); ] ... esac

Cada patrón examinado se expande usando expansión de tilde, expansión de parámetros y variables, sustitución aritmética, sustitución de comandos y sustitución de procesos.

No «Expansión de nombre de ruta»

Por lo tanto: un patrón NO se expande con «Expansión de nombre de ruta».

Por lo tanto: un patrón NO puede contener "|" dentro. Solo: dos patrones se pueden unir con el "|".

Esto funciona:

s1="foo"; s2="bar"    # or even s1="*foo*"; s2="*bar*"

read choice
case $choice in
    $s1|$s2 )     echo "Two val choice $choice"; ;;  # not "$s1"|"$s2"
    * )           echo "A Bad  choice! $choice"; ;;
esac

Usando «Globbing extendido»

Sin embargo, wordse patterncombina con el uso de las reglas de "Expansión del nombre de ruta".
Y «Globbing extendido» aquí , aquí y, aquí permite el uso de patrones alternos ("|").

Esto también funciona:

shopt -s extglob

string='@(foo|bar)'

read choice
    case $choice in
        $string )      printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )      printf 'Two val choice %-20s' "$choice"; ;;
        *)             printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
echo

Contenido de cadena

El siguiente script de prueba muestra que el patrón que coincide con todas las líneas que contienen fooo en barcualquier lugar es '*$(foo|bar)*'o las dos variables $s1=*foo*y$s2=*bar*


Script de prueba:

shopt -s extglob    # comment out this line to test unset extglob.
shopt -p extglob

s1="*foo*"; s2="*bar*"

string="*foo*"
string="*foo*|*bar*"
string='@(*foo*|*bar)'
string='*@(foo|bar)*'
printf "%s\n" "$string"

while IFS= read -r choice; do
    case $choice in
        "$s1"|"$s2" )   printf 'A first choice %-20s' "$choice"; ;;&
        $string )   printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )   printf 'Two val choice %-20s' "$choice"; ;;
        *)      printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
    echo
done <<-\_several_strings_
f
b
foo
bar
*foo*
*foo*|*bar*
\"foo\"
"foo"
afooline
onebarvalue
now foo with spaces
_several_strings_
Comunidad
fuente
9

Puedes usar la extglobopción:

shopt -s extglob
string='@(foo|bar)'
choroba
fuente
Interesante; ¿Qué hace @(foo|bar)especial en comparación con foo|bar? Ambos son patrones válidos que funcionan igual cuando se escriben literalmente.
chepner
44
Ah no importa. |no es parte del patrón foo|bar, es parte de la sintaxis de la casedeclaración para permitir múltiples patrones en una cláusula. | Sin embargo, es parte del patrón extendido.
chepner
3

Necesita dos variables caseporque la |tubería o se analiza antes de que se expandan los patrones.

v1=foo v2=bar

case foo in ("$v1"|"$v2") echo foo; esac

foo

Los patrones de shell en las variables también se manejan de manera diferente cuando se citan o no:

q=?

case a in
("$q") echo question mark;;
($q)   echo not a question mark
esac

not a question mark
mikeserv
fuente
-1

Si desea una solución alternativa compatible con el tablero, puede escribir:

  string="foo|bar"
  read choice
  awk 'BEGIN{
   n=split("'$string'",p,"|");
   for(i=1;i<=n;i++)
    if(system("\
      case \"'$choice'\" in "p[i]")\
       echo \"You chose '$choice'\";\
       exit 1;;\
      esac"))
     exit;
   print "Bad choice"
  }'

Explicación: awk se usa para dividir "cadenas" y probar cada parte por separado. Si "choice" coincide con la parte probada actualmente p [i], el comando awk terminará con "exit" en la línea 11. Para la misma prueba, se utiliza el "caso" del shell (dentro de una llamada 'system') , según lo solicitado por terdon. Esto mantiene la posibilidad de modificar la prueba de "cadena", por ejemplo, a "foo * | bar" para que coincida también con "foooo" ("patrón de búsqueda"), según lo permita el "caso" del shell. Si, en cambio, preferiría expresiones regulares, podría omitir la llamada al "sistema" y utilizar "~" o "coincidencia" de awk.

Gerald Schade
fuente
Estimado votante, podría aprender mejor cómo evitar candidatos a soluciones inútiles si me dijera el motivo de su voto negativo.
Gerald Schade