Correspondência de regex em uma instrução Bash if

89

O que eu fiz de errado aqui?

Tentar corresponder qualquer string que contenha espaços, letras minúsculas, maiúsculas ou números. Caracteres especiais também seriam legais, mas acho que isso requer o escape de certos caracteres.

TEST="THIS is a TEST title with some numbers 12345 and special char *&^%$#"

if [[ "$TEST" =~ [^a-zA-Z0-9\ ] ]]; then BLAH; fi

Obviamente, isso só testa superior, inferior, números e espaços. Mas não funciona.

* ATUALIZAÇÃO *

Eu acho que deveria ter sido mais específico. Aqui está a linha de código real real.

if [[ "$TITLE" =~ [^a-zA-Z0-9\ ] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; fi

* ATUALIZAÇÃO *

./anm.sh: line 265: syntax error in conditional expression
./anm.sh: line 265: syntax error near `&*#]'
./anm.sh: line 265: `  if [[ ! "$TITLE" =~ [a-zA-Z0-9 $%^\&*#] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; return; fi'
Atomiklan
fonte
Qual shell você está realmente usando? / bin / sh? / bin / bash? / bin / csh?
Willem Van Onsem
8
É mais seguro colocar a regex em uma variável. re='...whatever...'; [[ $string =~ $re ]](sem aspas - este é um dos raros casos em que eles quebram algo que funcionaria sem eles).
Charles Duffy
3
Coloque aspas simples ao redor da tarefa. As aspas duplas não protegerão os caracteres especiais adequadamente.
tripleee de
Muito obrigado, Charles! Ainda está tudo bem não colocá-lo em uma variável, mas NÃO deve ser colocado entre aspas! Por exemplo: [[ $var =~ .* ]]para match regex .*(qualquer coisa). Eu acho que se você usar aspas, as próprias aspas são consideradas parte do regex ...
Stéphane,
4
peguei o resumo que encontrei: (1.) salve o padrão em uma variável usando aspas simples pattern='^hello[0-9]*$'(2.) na expressão de quadrado duplo se você precisar de correspondência de regex, NÃO indique o padrão porque a citação DESATIVA a correspondência de padrão de regex. (ou seja, a expressão [[ "$x" =~ $pattern ]]corresponderá usando regex e a expressão [[ "$x" =~ "$pattern" ]]desativa a correspondência de regex e é equivalente a[[ "$x" == "$pattern" ]] ).
Trevor Boyd Smith,

Respostas:

184

Há algumas coisas importantes que você deve saber sobre a [[ ]]construção do bash . O primeiro:

A divisão de palavras e a expansão do nome do caminho não são executadas nas palavras entre [[e ]]; Expansão til, expansão de parâmetro e variável, expansão aritmética, substituição de comando, substituição de processo e remoção de cotação são executadas.

A segunda coisa:

Um operador binário adicional, '= ~', está disponível, ... a string à direita do operador é considerada uma expressão regular estendida e correspondida de acordo ... Qualquer parte do padrão pode ser citada para forçar a correspondência como uma string .

Conseqüentemente, $vem qualquer lado do =~será expandido para o valor daquela variável, mas o resultado não será dividido por palavras ou expandido pelo nome do caminho. Em outras palavras, é perfeitamente seguro deixar as expansões variáveis ​​sem aspas no lado esquerdo, mas você precisa saber que as expansões variáveis ​​acontecerão no lado direito.

Então, se você escrever: [[ $x =~ [$0-9a-zA-Z] ]]o $0interior do regex à direita será expandido antes do regex é interpretado, o que provavelmente fará com que o regex para não compilar (a menos que a expansão das $0extremidades com um símbolo de dígitos ou pontuação cujo valor ASCII é menor do que um dígito). Se você citar o lado direito da mesma maneira [[ $x =~ "[$0-9a-zA-Z]" ]], o lado direito será tratado como uma string comum, não uma regex (e $0ainda será expandido). O que você realmente quer neste caso é[[ $x =~ [\$0-9a-zA-Z] ]]

Da mesma forma, a expressão entre [[e ]]é dividida em palavras antes que a regex seja interpretada. Portanto, os espaços no regex precisam ter escape ou entre aspas. Se você queria para combinar letras, números ou espaços que você poderia usar: [[ $x =~ [0-9a-zA-Z\ ] ]]. Outros caracteres também precisam de escape, como #, o que iniciaria um comentário se não fosse citada. Claro, você pode colocar o padrão em uma variável:

pat="[0-9a-zA-Z ]"
if [[ $x =~ $pat ]]; then ...

Para regexes que contêm muitos caracteres que precisariam ser escapados ou citados para passar pelo lexer do bash, muitas pessoas preferem esse estilo. Mas cuidado: neste caso, você não pode citar a expansão da variável:

# This doesn't work:
if [[ $x =~ "$pat" ]]; then ...

Finalmente, acho que o que você está tentando fazer é verificar se a variável contém apenas caracteres válidos. A maneira mais fácil de fazer essa verificação é certificar-se de que não contém um caractere inválido. Em outras palavras, uma expressão como esta:

valid='0-9a-zA-Z $%&#' # add almost whatever else you want to allow to the list
if [[ ! $x =~ [^$valid] ]]; then ...

!nega o teste, transformando-o em um operador "não corresponde", e uma [^...]classe de caractere regex significa "qualquer caractere diferente de ...".

A combinação de expansão de parâmetro e operadores regex pode tornar a sintaxe da expressão regular do bash "quase legível", mas ainda existem algumas pegadinhas. (Não há sempre?) Uma é que você não poderia colocar] em $valid, mesmo que $validforam citados, exceto no início. (Essa é uma regra Posix regex: se você quiser incluir ]em uma classe de caractere, ela precisa ir no início. -Pode ir no início ou no final, então se você precisa de ambos ]e -, você precisa começar ]e terminar com -, levando à regex "Eu sei o que estou fazendo" emoticon: [][-])

rici
fonte
6
Quero apenas salientar que "! ~ É o" não corresponde ao "operador" não é verdade. Use if ! [[ $x =~ $y ]]ouif [[ ! $x =~ $y ]]
álcool de
shellchecker discorda ...SC2076: Don't quote rhs of =~, it'll match literally rather than as a regex.
Leonardo
4
@leonard: como isso difere da minha declaração "você não pode citar a expansão da variável" e do comentário "Isso não funciona"? O que não está claro nisso?
rici
1
@jinbeomhong: a própria expressão é separada em palavras como de costume, usando espaços em branco. Mas as expansões de parâmetros e comandos não são divididas por palavras.
rici
1
@jinbeomhong: Não estou dizendo nada diferente do manual do bash. "as palavras entre [[e ]]" são analisadas fora do texto do programa, da mesma forma que as linhas de comando são analisadas em palavras. Ao contrário das linhas de comando, porém, as palavras não são divididas após as expansões.
rici
30

Caso alguém queira um exemplo usando variáveis ​​...

#!/bin/bash

# Only continue for 'develop' or 'release/*' branches
BRANCH_REGEX="^(develop$|release//*)"

if [[ $BRANCH =~ $BRANCH_REGEX ]];
then
    echo "BRANCH '$BRANCH' matches BRANCH_REGEX '$BRANCH_REGEX'"
else
    echo "BRANCH '$BRANCH' DOES NOT MATCH BRANCH_REGEX '$BRANCH_REGEX'"
fi
Oliver Pearmain
fonte
13

Eu prefiro usar [:punct:]para isso. Além disso, a-zA-Z09-9pode ser apenas [:alnum:]:

[[ $TEST =~ ^[[:alnum:][:blank:][:punct:]]+$ ]]
konsolebox
fonte
-1

Ou você pode estar olhando para esta pergunta porque aconteceu de você cometer um erro de digitação bobo como eu fiz e ter o = ~ revertido para ~ =

usuário linux tímido
fonte