¿Por qué no se trata `|` literalmente en un patrón global?

13

Mi pregunta proviene de ¿Cómo el almacenamiento de la expresión regular en una variable de shell evita problemas al citar caracteres que son especiales para el shell? .

  1. ¿Por qué hay un error?

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'

    Dentro [[ ... ]]del segundo operando de =se espera que haya un patrón globbing.

    ¿ a|bNo es un patrón de globo válido? ¿Puedes señalar qué regla de sintaxis viola?

  2. Algunos comentarios a continuación señalan que |se interpreta como tubería.

    Luego, cambiar el =patrón glob =~por el patrón regex hace que |funcione

    $ [[ $a =~ a|b ]]

    Aprendí de Learning Bash p180 en mi publicación anterior que |se reconoce como pipe al comienzo de la interpretación, incluso antes de cualquier otro paso de interpretación (incluido analizar las expresiones condicionales en los ejemplos). Entonces, ¿cómo se |puede reconocer como operador de expresiones regulares cuando se usa =~, sin ser reconocido como tubería en un uso no válido, como cuando se usa =? Eso me hace pensar que el error de sintaxis en la parte 1 no significa que |se interprete como una tubería.

    Cada línea que el shell lee desde la entrada estándar o un script se llama una tubería; contiene uno o más comandos separados por cero o más caracteres de canalización (|). Para cada canalización que lee, el shell lo divide en comandos, configura la E / S para la canalización y luego hace lo siguiente para cada comando (Figura 7-1):

Gracias.

Tim
fuente
1
Tenga en cuenta que en algunas versiones de bash, el análisis de extglob (donde |es especial) está activado de forma predeterminada en el lado derecho de [[ $var = $pattern ]]. Sería interesante aislar las versiones y las shoptconfiguraciones de opciones donde se ve este comportamiento, si son solo aquellas donde extglobestá activado, ya sea de forma predeterminada o configuración explícita, bueno, ahí estamos.
Charles Duffy
2
Por cierto, si quisieras descartar algo más exhaustivamente el caso de que el carácter de la tubería interfiera con una etapa previa de análisis (que estoy de acuerdo no está sucediendo, pero no es tan obvio para el lector como podría ser), use pattern='a|b'y luego expanda sin $patterncotizar en el RHS.
Charles Duffy
@CharlesDuffy, ese fue el punto que se hizo en las preguntas y respuestas de las cuales esta pregunta es un seguimiento.
Stéphane Chazelas
Ahh - el contexto tiene sentido; y tu respuesta aquí es sobresaliente. Gracias en ambos sentidos.
Charles Duffy
Tim, ¿alguna de las respuestas a continuación responde tu pregunta? Por favor considere aceptar uno si es así. ¡Gracias!
Jeff Schaller

Respuestas:

13

No hay una buena razón por la cual

[[ $a = a|b ]]

Debería informar un error en lugar de probar si $ a es la a|bcadena, mientras [[ $a =~ a|b ]]que no devuelve un error.

La única razón es que |generalmente es (fuera y dentro [[ ... ]]) un personaje especial. En esa [[ $a =posición, bashespera un tipo de token que sea una PALABRA normal, como los argumentos o los objetivos de las redirecciones en una línea de comando de shell normal (pero como si la extglobopción se hubiera habilitado desde bash 4.1).

(por WORD aquí, me refiero a una palabra en una gramática de shell hipotética como la descrita por la especificación POSIX , que es algo que el shell analizaría como un token en una línea de comando de shell simple, no otra definición de palabras como el inglés uno de una secuencia de letras o una secuencia de caracteres no espaciado. foo"bar baz", $(echo x y), son dos de estas WORD s).

En una línea de comando de shell normal:

echo a|b

Se echo acanaliza a b. a|bno es una PALABRA , son tres fichas: una a PALABRA , una |ficha y una ficha de b PALABRA .

Cuando se usa [[ $a = a|b ]], bashespera una PALABRA que obtiene ( a), pero luego encuentra un |token inesperado que causa el error.

Curiosamente, bashno se queja en:

[[ $a = a||b ]]

Debido a que ahora es un atoken seguido de un ||token seguido de b, entonces se analiza de la misma manera que:

[[ $a = a || b ]]

Cuál es la prueba que $aes ao que la bcadena no está vacía.

Ahora en:

[[ $a =~ a|b ]]

bashno puede tener la misma regla de análisis. Tener la misma regla de análisis significaría que lo anterior daría un error y que sería necesario citar eso |para garantizar que a|bsea ​​una sola PALABRA . Pero, desde bash 3.2, si lo haces:

[[ $a =~ 'a|b' ]]

Eso ya no coincide con la a|bexpresión regular sino con la a\|bexpresión regular. Es decir, las citas de shell tienen el efecto secundario de eliminar el significado especial de los operadores regexp. Es una característica, por lo que el comportamiento es similar al de [[ $a = "?" ]]uno, pero los patrones comodín (utilizados en [[ $a = pattern ]]) son PALABRAS de shell (utilizadas en globos, por ejemplo), mientras que las expresiones regulares no lo son.

Así que bashtiene que tratar a todos los operadores de expresiones regulares extendidas que son por lo demás normalmente caracteres especiales como shell |, (, )diferente al analizar un argumento del =~operador.

Aún así, tenga en cuenta que mientras

 [[ $a =~ (ab)*c ]]

ahora trabaja,

 [[ $a =~ [)}] ]]

no lo hace Necesitas:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]

Que en versiones anteriores de bashcoincidiría incorrectamente en la barra invertida. Ese fue arreglado, pero

 [[ $a =~ [^]')'] ]]

No no coincidir en la barra invertida como debería, por ejemplo. Debido a que bashno se da cuenta de que )está dentro de los corchetes, se escapa )para dar como resultado una [^]\)]expresión regular que coincide con cualquier carácter pero ], \y ).

ksh93 tiene errores mucho peores en ese frente.

En zsh, es una palabra de shell normal que se espera y citar operadores regexp no afecta el significado de los operadores regexp.

[[ $a =~ 'a|b' ]]

Está haciendo juego contra la a|bexpresión regular.

Eso significa =~que también se puede agregar al comando [/ test:

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'

(también funciona yash. Es =~necesario citarlo, zshya que =somethinghay un operador de shell especial allí).

bash 3.1 solía comportarse como zsh. Cambió en 3.2, presumiblemente para alinearse con ksh93(aunque bashfue el shell que apareció por primera vez [[ =~ ]]), pero aún puede hacerlo BASH_COMPAT=31o shopt -s compat31volver al comportamiento anterior (excepto que si [[ $a =~ a|b ]]bien devolvería un error en bash3.1, ya no lo hace en bash -O compat31con las nuevas versiones de bash).

Espero que aclare por qué dije que las reglas eran confusas y por qué usar:

[[ $a =~ $var ]]

ayuda incluso con la portabilidad a otros proyectiles.

Stéphane Chazelas
fuente
zsh también informa un error en [[ $a = a|b ]].
Isaac
@isaac, sí, ese es el punto que estoy haciendo aquí. a|bNo es una cáscara PALABRA aquí, es el a, |y btoken. Al igual echo a|bque no sale a|bo no expande un a|bglobo, debe citar eso, |ya que es un carácter de shell especial que no es válido en ese contexto. [[ $a = (a|b) ]]funcionaría como echo (a|b)funcionaría como (a|b)es un operador comodín zsh.
Stéphane Chazelas
La redacción y la explicación en su respuesta solo nombran bash. Esa no es toda la verdad.
Isaac
11

Pegotes estándar ( "expansión de nombre de archivo") son: *, ?y [ ... ]. |no es un operador glob válido en configuraciones estándar (no extglob).

Tratar:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Jeff Schaller
fuente
1
Gracias. Pero, ¿por qué no se |interpreta literalmente? ¿Por qué hay un error de sintaxis?
Tim
1
No fue citado.
Jeff Schaller
3
En la configuración estándar, |¿no es un operador global, por lo que no se |interpreta literalmente sin ser citado? Entonces, ¿por qué hay un error de sintaxis?
Tim
1
|es un personaje de control; nunca se trata como un carácter literal de la misma manera que una letra o número.
chepner
3
Porque en ese modo, el shell no esperaba un carácter de redirección de canalización en medio de un [[]] aún no cerrado. [[ $a = ano es un comando válido cuya salida se pueda canalizar a otro proceso (al menos eso es lo que el shell pensó que estaba tratando de hacer).
Jason C
5

Si desea una coincidencia de expresiones regulares, la prueba sería:

[[ "$a" =~ a|b ]]
Abrazo de la muerte
fuente
@Tim Debería estar abriendo nuevas preguntas, no editando continuamente su pregunta actual.
cabeza de jardín
@gardenhead: Mi actualización es para aclarar mis preguntas, en lugar de cambiarlas, en caso de que se las pierda. La segunda parte que agregué es mostrar la explicación de un comentario sobre mi pregunta original (por qué el error de sintaxis) no es correcta.
Tim