Configurando opções do bash em um comando composto

9

Eu descobri que definir a extglobopção de shell em um composto composto resulta em falha dos anti-globs subsequentes. As opções de shell precisam ser definidas fora dos comandos compostos? Eu não vi uma indicação de tal requisito nas páginas de manual do bash.

Como exemplo, o seguinte script funciona bem (impressões a.0 a.1):

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

No entanto, se as duas últimas linhas forem executadas como um comando composto, o script falhará com o seguinte erro:

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

Isso foi testado usando as versões bash de 4.2 a 4.4 e com uma variedade de comandos compostos :

(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) chaves - { }

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

(3) subcasca - ( ):

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

Em todos os casos, se o shoptcomando for movido para fora do comando composto, o script será bem-sucedido.

user001
fonte

Respostas:

14

Nenhuma parte de um comando composto é executada antes de todo o comando ser analisado, o que é obliquamente documentado na seção Operação do Shell do manual: todas as tokens e análises acontecem antes da execução do comando "the". Possibilitandoextglob altera a sintaxe do idioma adicionando novos operadores de correspondência de padrões, que são reconhecidos durante a tokenização.

Como o shoptcomando não foi executado no momento em que !(foi atingido, ele é visto como uma tentativa de expansão do histórico ( !) ou o operador de controle (, em vez de !(ser visto como um único item (e o mesmo para @(etc), exceto que não há expansão do histórico há).

Quando a análise atinge esse token, ambos os casos geralmente são um erro, embora !(...)no início de uma linha ou depois haja um timepipeline de subshell negado. " unexpected token `('" significa que não foi possível aceitar uma expressão de subcamada nesse local.

Isso é um pouco chato quando você deseja que o extglob seja ativado apenas temporariamente em um subshell. Uma solução é definir uma função que usa o glob você quer, em seguida, re-disable extglob, e , em seguida, chamar a função do subnível com extglob habilitado:

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

É muito desajeitado.


O mesmo efeito ocorre se você criar um alias dentro de um comando composto, porque eles também serão processados ​​antes da análise:

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

será impresso foo: command not founde, em seguida xyz, porque o alias é criado, mas não está disponível até que ifseja feito.

Michael Homer
fonte
Obrigado - eu não percebi que os comandos compostos foram analisados ​​como uma unidade antes da execução. O comportamento agora faz sentido.
user001 02/06/19
3
@ user001 Eu não seria tão caridoso em dizer que faz sentido. Mudar o modo do lexer durante a análise não é algo inédito e outros idiomas gostam Cou perlsão capazes de fazê-lo bem. Observe que não fazer parte de um comando composto não é suficiente, shopt -s extglobtambém precisa estar em uma linha separada. Veja também a discussão e os exemplos aqui
mosvy
@mosvy: eu quis dizer que o comportamento observado faz sentido quando se percebe que o bash analisa um comando composto como uma unidade (não que as limitações do analisador sejam necessariamente razoáveis). Obrigado por vincular à discussão no outro post.
User001