¿Cómo puedo saber mediante programación si un nombre de archivo coincide con un patrón glob de shell?

13

Me gustaría saber si una cadena $stringcoincidiría con un patrón global $pattern. $stringpuede o no ser el nombre de un archivo existente. ¿Cómo puedo hacer esto?

Asumir los siguientes formatos para mis cadenas de entrada:

string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

Me gustaría encontrar un lenguaje bash que determina si $stringsería igualado por $pattern1, $pattern2o cualquier otro patrón global arbitraria. Esto es lo que he intentado hasta ahora:

  1. [[ "$string" = $pattern ]]

    Esto casi funciona, excepto que $patternse interpreta como un patrón de cadena y no como un patrón global.

  2. [ "$string" = $pattern ]

    El problema con este enfoque es que $patternse expande y luego se realiza una comparación de cadenas entre $stringy la expansión de $pattern.

  3. [[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]

    Este funciona, pero solo si $stringcontiene un archivo que existe.

  4. [[ $string =~ $pattern ]]

    Esto no funciona porque el =~operador hace $patternque se interprete como una expresión regular extendida, no como un patrón global o comodín.

jayhendren
fuente
2
El problema con el que te encontrarás es que {bar,baz}no es un patrón. Es la expansión de parámetros. La diferencia sutil pero crítica {bar,baz}se expande muy pronto en múltiples argumentos, bary baz.
Patrick
Si el shell puede expandir los parámetros, entonces seguramente puede decir si una cadena es una expansión potencial de un globo.
Jayhendren
prueba esto a = ls /foo/* ahora puedes igualar en un
Hackaholic
1
@Patrick: después de leer la página de manual de bash, he aprendido que en foo/{bar,baz}realidad es una expansión de llaves (no una expansión de parámetros) mientras que foo/*es una expansión de nombre de ruta. $stringes la expansión de parámetros. Todo esto se hace en diferentes momentos y por diferentes mecanismos.
jayhendren
@jayhendren @Patrick tiene razón, y luego aprendiste que tu pregunta en última instancia no es lo que el título lleva a creer. Por el contrario, desea hacer coincidir una cadena con varios tipos de patrones. Si desea que coincida estrictamente con un patrón caseglobal , la instrucción realiza la Expansión del nombre de ruta ("globalización") según el manual de Bash.
Mike S

Respuestas:

8

No hay una solución general para este problema. La razón es que, en bash, la expansión de llaves (es decir, {pattern1,pattern2,...}y la expansión de nombre de archivo (también conocidos como patrones globales) se consideran cosas separadas y se expanden bajo diferentes condiciones y en diferentes momentos. Aquí está la lista completa de expansiones que realiza bash:

  • expansión de llaves
  • expansión tilde
  • parámetro y expansión variable
  • sustitución de comando
  • expansión aritmética
  • división de palabras
  • expansión de nombre de ruta

Dado que solo nos preocupamos por un subconjunto de estos (quizás expansión de llaves, tilde y nombre de ruta), es posible usar ciertos patrones y mecanismos para restringir la expansión de manera controlable. Por ejemplo:

#!/bin/bash
set -f

string=/foo/bar

for pattern in /foo/{*,foo*,bar*,**,**/*}; do
    [[ $string == $pattern ]] && echo "$pattern matches $string"
done

La ejecución de este script genera el siguiente resultado:

/foo/* matches /foo/bar
/foo/bar* matches /foo/bar
/foo/** matches /foo/bar

Esto funciona porque set -fdeshabilita la expansión del nombre de ruta, por lo que solo la expansión de llaves y la expansión de tilde ocurren en la declaración for pattern in /foo/{*,foo*,bar*,**,**/*}. Luego podemos usar la operación [[ $string == $pattern ]]de prueba para probar contra la expansión del nombre de ruta después de que la expansión de la llave ya se haya realizado.

jayhendren
fuente
7

No creo que {bar,baz} sea un patrón glob de concha (aunque ciertamente lo /foo/ba[rz]es), pero si quieres saber si $stringcoincide $patternpuedes hacerlo:

case "$string" in 
($pattern) put your successful execution statement here;;
(*)        this is where your failure case should be   ;;
esac

Puedes hacer tantas como quieras:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac
mikeserv
fuente
1
Hola @mikeserv, como se indicó en los comentarios y la respuesta que proporcioné anteriormente, ya he aprendido que lo que dices es cierto, {bar,baz}no es un patrón global. Ya he encontrado una solución a mi pregunta que tiene esto en cuenta.
jayhendren
3
+1 Esto responde la pregunta exactamente como se da en el título y la primera oración. aunque la pregunta luego combina otros patrones con patrones de globos de concha.
Mike S
3

Como Patrick señaló que necesita un "tipo diferente" de patrón:

[[ /foo/bar == /foo/@(bar|baz) ]]


string="/foo/bar"
pattern="/foo/@(bar|baz)"
[[ $string == $pattern ]]

Las cotizaciones no son necesarias allí.

Hauke ​​Laging
fuente
Ok, esto funciona, pero estrictamente hablando, no responde mi pregunta. Por ejemplo, me gustaría considerar patrones que provienen de otra fuente, es decir, los patrones están fuera de mi control.
Jayhendren
@jayhendren Entonces probablemente primero tengas que convertir el patrón entrante a los que acepta bash.
Hauke ​​Laging
Entonces, todo lo que realmente ha hecho es transformar mi pregunta de "¿cómo puedo saber si un nombre de archivo es una expansión potencial de una expresión" a "cómo convierto los patrones de nombre de archivo de estilo bash normales en patrones de glob extendido de estilo bash".
jayhendren
@jayhendren Teniendo en cuenta que lo que quieres parece imposible "todo lo que realmente has hecho" me suena un poco extraño, pero tal vez eso sea solo una cuestión de idioma extranjero. Si desea hacer esta nueva pregunta, debe explicar cómo se ven los patrones de entrada. Tal vez es una sedoperación simple .
Hauke ​​Laging
1
@HaukeLaging es correcto. Las personas que vienen aquí para descubrir cómo hacer coincidir los patrones globales (también conocido como "Expansión del nombre de ruta") pueden confundirse, como lo hice yo, porque el título dice "patrón global glob" pero el contenido de su pregunta utiliza un patrón no global . En algún momento, Jayhendren se enteró de la diferencia, pero su confusión inicial es lo que causó que Hauke ​​respondiera de la manera que lo hizo.
Mike S
1

Usaría grep así:

#!/bin/bash
string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

if echo $string | grep -e "$pattern1" > /dev/null; then
    echo $string matches $pattern1
fi

if echo $string | grep -e "$pattern2" > /dev/null; then
    echo $string matches $pattern2
fi

Lo que da la salida:

./test2.bsh 
/foo/bar matches /foo/*
musaraña
fuente
-2

en zsh:

[[ $string = $~pattern ]] && print true
llua
fuente
1
La pregunta establece que se usará el shell bash.
Jayhendren