Por que o `|` não é tratado literalmente em um padrão global?

13

Minha pergunta vem de Como o armazenamento da expressão regular em uma variável do 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'

    Espera-se que dentro [[ ... ]]do segundo operando de =um padrão globbing.

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

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

    Mudando =para o padrão glob para=~ para o padrão regex faz o |trabalho

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

    Aprendi com o Learning Bash p180 em minha postagem anterior, que |é reconhecida como pipe no início da interpretação, mesmo antes de qualquer outra etapa de interpretação (incluindo a análise das 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 pipe.

    Cada linha que o shell lê da entrada padrão ou de um script é chamada de pipeline; ele contém um ou mais comandos separados por zero ou mais caracteres de barra vertical (|). Para cada pipeline que lê, o shell divide-o em comandos, configura a E / S para o pipeline e, em seguida, faz o seguinte para cada comando (Figura 7-1):

Obrigado.

Tim
fonte
1
Observe que em algumas versões do bash, a análise extglob (onde |é especial) está ativada por padrão no lado direito de [[ $var = $pattern ]]. Seria interessante isolar as versões e shoptconfigurações de opções em que esse comportamento é visto - se são apenas aquelas em que extglobestá, por padrão ou configuração explícita, bem, aqui estamos.
Charles Duffy
2
BTW, se você quiser excluir de maneira um pouco mais abrangente o caso do caractere de pipe que interfere com um estágio anterior da análise (o que eu concordo que não está acontecendo, mas não é tão óbvio para o leitor quanto possível), você use pattern='a|b'e expanda sem $patternaspas no RHS.
Charles Duffy
@CharlesDuffy, esse foi o argumento apresentado nas perguntas e respostas, que esta pergunta segue.
Stéphane Chazelas
Ahh - o contexto faz sentido; e sua resposta aqui é excelente. Obrigado em ambos os aspectos.
Charles Duffy
Tim, alguma das respostas abaixo responde à sua pergunta? Por favor, considere aceitar um se sim. Obrigado!
Jeff Schaller

Respostas:

13

Não há uma boa razão para

[[ $a = a|b ]]

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

A única razão é que |geralmente é (fora e dentro [[ ... ]]) um caractere especial. Nessa [[ $a =posição, bashespera-se um tipo de token que seja uma PALAVRA normal, como os argumentos ou os destinos de redirecionamentos em uma linha de comando normal do shell (mas como se oextglob opção tivesse sido ativada desde o bash 4.1).

(por WORD aqui, refiro-me a uma palavra em uma gramática hipotética de shell como a descrita pela especificação POSIX , isso é algo que o shell analisaria como um token em uma simples linha de comando do shell, não outra definição de palavras como o inglês um de uma sequência de letras ou de uma sequência de caracteres sem espaçamento. foo"bar baz", $(echo x y), são dois tais PALAVRA s).

Em uma linha de comando normal do shell:

echo a|b

É echo acanalizado para b. a|bnão é uma PALAVRA , são três fichas: uma a PALAVRA , um |token e um token da b PALAVRA .

Quando usado [[ $a = a|b ]], bashespera uma PALAVRA que ele recebe ( a), mas encontra um |token inesperado que causa o erro.

Curiosamente, bashnão se queixa em:

[[ $a = a||b ]]

Como agora é um atoken seguido de um ||token seguido de b, ele é analisado da mesma maneira que:

[[ $a = a || b ]]

Que está testando que $aé aou que a bstring é não vazio.

Agora em:

[[ $a =~ a|b ]]

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

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

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

Então, bashtem que tratar todos os operadores de expressões regulares estendidas que são de outra maneira normalmente especial caracteres shell como |, (, )diferente ao analisar um argumento da=~ operador.

Ainda assim, observe que enquanto

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

agora funciona

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

não. Você precisa:

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

O qual nas versões anteriores de bashcorresponderia incorretamente na barra invertida. Aquele foi consertado, mas

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

Será que não corresponder em barra invertida como deveria, por exemplo. Como bashfalha ao perceber que )está entre parênteses, escapa-o )para resultar em uma [^]\)]regexp que corresponde a qualquer caractere ], exceto , \e) .

ksh93 tem bugs muito piores nessa frente.

Em zsh, é uma palavra shell normal que é esperada e citar operadores regexp não afeta o significado de operadores regexp.

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

É compatível com a a|bregexp.

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

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

(também funciona yash. As =~necessidades devem ser citadas zshcomo =somethingum operador de shell especial).

bash 3.1 costumava se comportar como zsh. Ele mudou na versão 3.2, presumivelmente para se alinhar ksh93(embora tenha bashsido o shell que surgiu pela primeira vez [[ =~ ]]), mas você ainda pode fazer BASH_COMPAT=31ou shopt -s compat31reverter para o comportamento anterior (exceto que embora [[ $a =~ a|b ]]retornasse um erro na bashversão 3.1, isso não acontece mais) nas bash -O compat31versões mais recentes do bash).

Espero que esclareça por que eu disse que as regras eram confusas e por que usar:

[[ $a =~ $var ]]

ajuda inclusive com portabilidade para outras conchas.

Stéphane Chazelas
fonte
O zsh também está relatando um erro [[ $a = a|b ]].
Isaac
@isaac, sim, esse é o ponto que estou fazendo aqui. a|bnão é um shell PALAVRA aqui, é a a, |e btoken. Como echo a|bnão gera a|bou não expande um a|bglobo, é necessário citar isso |, pois é um caractere de shell especial inválido nesse contexto. [[ $a = (a|b) ]]funcionaria como echo (a|b)funcionaria como (a|b)é um operador curinga zsh.
Stéphane Chazelas
O texto e a explicação da sua resposta apenas nomeiam bash. Essa não é a verdade completa.
Isaac
11

Globs padrão ( "expansão filename") são: *, ?, e [ ... ].|não é um operador glob válido nas configurações padrão (não extglob).

Experimentar:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Jeff Schaller
fonte
1
Obrigado. Mas por que não é |interpretado literalmente? Por que há um erro de sintaxe?
Tim
1
Não foi citado.
Jeff Schaller
3
Em configurações padrão, |não é um operador glob, portanto não é |interpretado literalmente sem ser citado? Então, por que há um erro de sintaxe?
Tim
1
|é um caractere de controle; nunca é tratado como um caractere literal da mesma maneira que uma letra ou número.
chepner
3
Porque nesse modo, o shell não esperava um caractere de redirecionamento de canal no meio de um [ainda não fechado]. [[ $a = anão é um comando válido cuja saída possa ser canalizada para outro processo (pelo menos é o que o shell pensou que você estava tentando fazer).
Jason C
5

Se você deseja que uma regex corresponda, o teste seria:

[[ "$a" =~ a|b ]]
Aperto de morte
fonte
@ Tim Você deve abrir novas perguntas, não editando continuamente sua pergunta atual.
gardenhead
@ Gardenhead: Minha atualização é para esclarecer minhas perguntas, em vez de alterá-las, caso você as perca. A segunda parte que adicionei é mostrar a explicação de um comentário sobre a minha pergunta original (por que o erro de sintaxe) não está correta.
Tim