Qualquer expressão regular que não seja espaço em branco

4

Estou tentando combinar uma string com uma expressão regular dentro de uma instrução if no bash. Código abaixo:

var='big'
If [[ $var =~ ^b\S+[a-z]$ ]]; then 
echo $var
else 
echo 'none'
fi

A correspondência deve ser uma string que comece com 'b' seguida por um ou mais caracteres que não sejam espaços em branco e que terminem em uma letra a-z. Eu posso combinar o início e o fim da string, mas o \ S não está funcionando para coincidir com os caracteres que não são espaços em branco. Agradecemos antecipadamente pela ajuda.

    
por Fxbaez 26.12.2015 / 20:05

2 respostas

12

Em sistemas não-GNU, o que segue explica por que \S falha:

O \S faz parte de um PCRE (Perl Compatible Regular Expressions). Não faz parte do BRE (expressões regulares básicas) ou do ERE (Expressões regulares estendidas) usado em shells.

O operador bash =~ dentro do teste de suporte duplo [[ usa ERE.

Os únicos caracteres com significado especial em ERE (em oposição a qualquer caractere normal) são .[\()*+?{|^$ . Não há S como especial. Você precisa construir o regex a partir de elementos mais básicos:

regex='^b[^[:space:]]+[a-z]$'

Onde a expressão de colchete [^[:space:]] é equivalente à \S Expressões PCRE :

Os caracteres padrão \s são agora HT (9), LF (10), VT (11), FF (12), CR (13) e espaço (32).

O teste seria:

var='big'            regex='^b[^[:space:]]+[a-z]$'

[[ $var =~ $regex ]] && echo "$var" || echo 'none'

No entanto, o código acima corresponderá a bißß , por exemplo. Como o intervalo [a-z] incluirá outros caracteres além de abcdefghijklmnopqrstuvwxyz se a localidade selecionada for (UNICODE). Para evitar esse problema, use:

var='bißß'            regex='^b[^[:space:]]+[a-z]$'

( LC_ALL=C;
  [[ $var =~ $regex ]]; echo "$var" || echo 'none'
)

Por favor, esteja ciente de que o código irá corresponder aos caracteres apenas na lista: abcdefghijklmnopqrstuvwxyz na última posição do personagem, mas ainda irá coincidir com muitos outros no meio: por exemplo, bég .

Ainda assim, esse uso de LC_ALL=C afetará o outro intervalo de regex: [[:space:]] corresponderá somente aos espaços do idioma C.

Para resolver todos os problemas, precisamos manter cada regex separado:

reg1=[[:space:]]   reg2='^b.*[a-z]$'           out=none

if                 [[ $var =~ $reg1 ]]  ; then out=none
elif   ( LC_ALL=C; [[ $var =~ $reg2 ]] ); then out="$var"
fi
printf '%6.8s\t|' "$out"

Qual lê como:

  • Se a entrada (var) não tiver espaços (no local atual), então
  • verifique se começa com b e termina em a-z (na localidade C).

Observe que ambos os testes são feitos nos intervalos positivos (em oposição a um intervalo "não"). A razão é que negar um par de personagens abre muito mais correspondências possíveis. O UNICODE v8 tem 120.737 caracteres já atribuídos. Se um intervalo nega 17 caracteres, ele aceita outros 120720 caracteres possíveis, que podem incluir muitos caracteres de controle não imprimíveis.

Deve ser uma boa ideia limitar o intervalo de caracteres que os personagens do meio podem ter (sim, esses não serão espaços, mas podem ser qualquer outra coisa).

    
por 26.12.2015 / 22:48
6
[[ $var =~ ^b[^[:space:]]+[abcdefghijklmnopqrstuvwxyz]$ ]]

O que [a-z] corresponde depende da localidade e geralmente não é (somente) um dos abcdefghijklmnopqrstuvwxyz .

O perl (espaços horizontais e verticais) de \S (espaços horizontais e verticais) agora também reconhecidos por outros mecanismos de expressão regular é [^[:space:]] em EREs POSIX e bash.

bash usa a biblioteca regexp do sistema para corresponder a essas expressões regulares, mas mesmo em sistemas (como os GNUs recentes) onde os regexps têm um operador \S , isso não funcionará porque:

[[ x = \S ]]

bash chamadas regcomp("S") e com:

[[ x = '\S' ]]

bash chamadas regcomp("\S") (duas barras invertidas).

No entanto, com bash-3.1 ou se você ativar a compatibilidade bash-3.1 com shopt -s compat31 , então:

[[ x = '\S' ]]

funcionará (corresponderá a um caractere sem espaçamento) em sistemas nos quais os EREs suportam \S .

$ bash -c "[[ x =~ '\S' ]]" || echo no
no
$ bash -O compat31 -c "[[ x =~ '\S' ]]" && echo yes
yes

Outra opção seria colocar o regexp em uma variável:

$ a='\S' bash -c '[[ x =~ $a ]]' && echo yes
yes

Mais uma vez, isso só funciona em sistemas que suportam o perl-like \S em seus regexps.

O equivalente POSIX ao código bash -specific, seria:

if expr " $var" : \
        ' b[^[:space:]]\{1,\}[abcdefghijklmnopqrstuvwxyz]$' \
   > /dev/null; then
  printf '%s\n' "$var"
else
  echo none
fi

Ou:

case $var in
  ([!b]* | *[!abcdefghijklmnopqrstuvwxyz] | *[[:space:]]* | "" | ? | ??)
    echo none;;
  (*) printf '%s\n' "$var"
esac
    
por 26.12.2015 / 21:34