Por que não é '|' tratado literalmente em um padrão glob?

12

Minha pergunta vem de Como o armazenamento da expressão regular em uma variável shell evita problemas com a citação de caracteres especiais para o shell? .

  1. Por que há um erro:

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

    Dentro de [[ ... ]] , espera-se que o segundo operando de = seja um padrão globbing.

    O a|b não é um padrão de globbing válido? Você pode apontar quais regra de sintaxe viola?

  2. Alguns comentários abaixo indicam que | é interpretado como pipe.

    Em seguida, alterar = para o padrão glob para =~ para o padrão de expressão regular | work

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

    Eu aprendi com Bash de aprendizado p180 no meu post anterior de que | é reconhecido como pipe no início de interpretação, mesmo antes de quaisquer outras etapas de interpretação (incluindo analisar as expressões condicionais nos exemplos). Então, como pode | ser reconhecido como operador regex ao usar =~ , sem ser reconhecido como pipe em uso inválido, assim como ao usar = ? Isso me faz pensar que o erro de sintaxe na parte 1 não significa que | seja interpretado como um canal.

    Each line that the shell reads from the standard input or a script is called a pipeline; it contains one or more commands separated by zero or more pipe characters (|). For each pipeline it reads, the shell breaks it up into commands, sets up the I/O for the pipeline, then does the following for each command (Figure 7-1):

Obrigado.

    
por Tim 27.07.2017 / 20:33

3 respostas

13

Não há uma boa razão para

[[ $a = a|b ]]

Deve relatar um erro em vez de testar se $ a é a string a|b , enquanto [[ $a =~ a|b ]] não retorna um erro.

A única razão é que | é geralmente (fora e dentro de [[ ... ]] ) um caractere especial. Nesse [[ $a = position, bash espera um tipo de token que é normal WORD como os argumentos ou alvos de redirecionamentos em uma linha de comando normal do shell (mas como se a opção extglob tinha sido ativado desde bash 4.1).

(por WORD aqui, eu me refiro a uma palavra em uma gramática de shell hipotética como o descrito pela especificação POSIX , que é algo que o shell iria analisar como um token em uma linha de comando simples, não outra definição de palavras como a inglesa de uma sequência de letras ou uma sequência de caracteres não espaçadores. foo"bar baz" , $(echo x y) , são dois desses WORD s).

Em uma linha de comando normal do shell:

echo a|b

É echo a canalizado para b . a|b não é um WORD , são três tokens: um a WORD , um | token e um b WORD token.

Quando usado em [[ $a = a|b ]] , bash espera um WORD que obtém ( a ), mas depois encontra um token | inesperado que causa o erro.

Curiosamente, bash não se queixa em:

[[ $a = a||b ]]

Porque agora é um token a seguido por um token || seguido por b , por isso é analisado da mesma forma que:

[[ $a = a || b ]]

Que está testando que $a é a ou que a b está vazia.

Agora, em:

[[ $a =~ a|b ]]

bash não pode ter a mesma regra de análise. Ter a mesma regra de análise significaria que o acima daria um erro e que seria necessário citar o | para garantir que a|b seja uma única PALAVRA . Mas, desde o bash 3.2, se você fizer:

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

Isso não corresponde mais ao a|b regexp, mas ao a\|b regexp. Ou seja, a citação de shell tem o efeito colateral de remover o significado especial dos operadores de expressão regular. É um recurso, portanto, o comportamento é semelhante ao [[ $a = "?" ]] one, mas os padrões de curinga (usados em [[ $a = pattern ]] ) são shell WORDS (usados em globs, por exemplo), enquanto os regexps não são. / p>

Portanto, bash tem que tratar todos os operadores regexp estendidos que normalmente são caracteres de shell especiais como | , ( , ) diferentemente ao analisar um argumento do operador =~ .

Ainda assim, observe que enquanto

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

agora funciona,

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

não. Você precisa de:

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

Qual das versões anteriores de bash corresponderia incorretamente na barra invertida. Aquele foi corrigido, mas

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

O não corresponde à barra invertida como deveria, por exemplo. Porque bash não percebe que ) está entre colchetes, então escapa a ) para resultar em [^]\)] regexp que corresponde a qualquer caractere, mas ] , \ e ) .

ksh93 tem erros muito piores nessa frente.

Em zsh , é uma palavra de shell normal que é esperada e a citação de operadores de expressão regular não afeta o significado de operadores de expressão regular.

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

Está correspondendo à a|b regexp.

Isso significa que o =~ também pode ser adicionado ao comando [ / test :

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

(também trabalhe em yash . O =~ precisa ser citado em zsh , já que =something é um operador de shell especial).

O

bash 3.1 costumava se comportar como zsh . Ele mudou em 3.2, provavelmente para alinhar com ksh93 (mesmo que bash tenha sido o shell que primeiro criou [[ =~ ]] ), mas você ainda pode fazer BASH_COMPAT=31 ou shopt -s compat31 para reverter para o comportamento anterior ( exceto que, enquanto [[ $a =~ a|b ]] retornaria um erro em bash 3.1, ele não existe mais em bash -O compat31 com versões mais recentes de bash ).

Espero que fique claro por que eu disse que as regras eram confusas e por que usar:

[[ $a =~ $var ]]

ajuda a incluir com portabilidade para outros shells.

    
por 28.07.2017 / 08:55
11

As globs padrão ("expansão de nome de arquivo") são: * , ? e [ ... ] . | não é um operador glob válido em configurações padrão (não extglob).

Tente:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
    
por 27.07.2017 / 20:45
5

Se você quiser uma correspondência de expressão regular, o teste seria:

[[ "$a" =~ a|b ]]
    
por 27.07.2017 / 21:54

Tags