Establecer opciones de bash en un comando compuesto

9

He descubierto que configurar la extglobopción de shell dentro de un compuesto compuesto da como resultado el fallo de los antiglobos posteriores. ¿Las opciones de shell deben establecerse fuera de los comandos compuestos? No vi una indicación de tal requisito en las páginas del manual de bash.

Como ejemplo, el siguiente script funciona bien (impresiones a.0 a.1):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
shopt -s extglob
ls "a."!(b*)

Sin embargo, si las dos últimas líneas se ejecutan como un comando compuesto, el script falla con el siguiente error:

syntax error near unexpected token `('
`    ls "a."!(b*)'

Esto se probó utilizando versiones bash de 4.2 a 4.4 y con una variedad de comandos compuestos :

(1) condicional - if

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
if true; then
    shopt -s extglob
    ls "a."!(b*)
fi

(2) llaves - { }

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
{
    shopt -s extglob
    ls "a."!(b*)
}

(3) subshell - ( ):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
(
    shopt -s extglob
    ls "a."!(b*)
)

En todos los casos, si shoptse mueve fuera del comando compuesto, el script tiene éxito.

user001
fuente

Respuestas:

14

Ninguna parte de un comando compuesto se ejecuta antes de que se analice todo el comando, lo cual se documenta indirectamente en la sección Operación de Shell del manual: todos los tokens y análisis se realizan antes de que se ejecute el comando "." La habilitación extglobcambia la sintaxis del idioma al agregar nuevos operadores de coincidencia de patrones, que se reconocen durante la tokenización.

Debido a que el shoptcomando no se ha ejecutado en el momento en que !(se alcanza, se ve como un intento de expansión del historial ( !) o como operador de control (, en lugar de !(ser visto como un solo elemento (y lo mismo para @(, etc., excepto que no hay expansión del historial allí).

Cuando el análisis alcanza ese token, cualquiera de los casos generalmente será un error, aunque !(...)al comienzo de una línea o después timees una tubería de subshell negada. " unexpected token `('" significa que no puede aceptar una expresión de subnivel en ese lugar.

Esto es algo molesto cuando desea que extglob esté habilitado solo temporalmente en una subshell. Una solución alternativa es definir una función que use el glob que desee, luego volver a deshabilitar extglob y luego llamar a la función desde la subshell con extglob habilitado:

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
    shopt -s extglob
    f
)

Es muy torpe.


El mismo efecto ocurre si crea un alias dentro de un comando compuesto, porque también se procesan antes de analizar:

if true
then
    alias foo=echo
    foo bar
fi
foo xyz

se imprimirá foo: command not found, y luego xyz, porque se crea el alias , pero no está disponible hasta que ifse haga.

Michael Homer
fuente
Gracias. No me di cuenta de que los comandos compuestos se analizaban como una unidad antes de la ejecución. El comportamiento ahora tiene sentido.
user001
3
@ user001 No sería tan caritativo decir que tiene sentido. Cambiar el modo del lexer durante el análisis no es nada inaudito, y otros idiomas les gusta Co perlpueden hacerlo bien. Tenga en cuenta que no ser parte de un comando compuesto no es suficiente, shopt -s extglobtambién tiene que estar en una línea separada. Vea también la discusión y ejemplos aquí
mosvy
@mosvy: quise decir que el comportamiento observado tiene sentido una vez que uno se da cuenta de que bash analiza un comando compuesto como una unidad (no es que las limitaciones del analizador sean necesariamente razonables). Gracias por enlazar a la discusión en la otra publicación.
user001