usar expressão regular em condição if no bash

87

Gostaria de saber a regra geral para usar expressão regular na cláusula if no bash?

Aqui está um exemplo

$ gg=svm-grid-ch  
$ if [[ $gg == *grid* ]] ; then echo $gg; fi  
svm-grid-ch  
$ if [[ $gg == ^....grid* ]] ; then echo $gg; fi  
$ if [[ $gg == ....grid* ]] ; then echo $gg; fi  
$ if [[ $gg == s...grid* ]] ; then echo $gg; fi  
$   

Por que os três últimos não coincidem?

Espero que você possa fornecer tantas regras gerais quanto possível, não apenas para este exemplo.

Tim
fonte

Respostas:

127

Ao usar um padrão glob, um ponto de interrogação representa um único caractere e um asterisco representa uma sequência de zero ou mais caracteres:

if [[ $gg == ????grid* ]] ; then echo $gg; fi

Ao usar uma expressão regular, um ponto representa um único caractere e um asterisco representa zero ou mais do caractere anterior. Portanto, " .*" representa zero ou mais de qualquer caractere, " a*" representa zero ou mais "a", " [0-9]*" representa zero ou mais dígitos. Outro útil (entre muitos) é o sinal de mais, que representa um ou mais dos caracteres anteriores. Portanto, " [a-z]+" representa um ou mais caracteres alfa minúsculos (no local C - e alguns outros).

if [[ $gg =~ ^....grid.*$ ]] ; then echo $gg; fi
Pausado até novo aviso.
fonte
Portanto, há duas maneiras de correspondência de string: padrão glob e expressão regular? Glob pettern não é usado apenas para nomes de arquivos? No bash, quando usar o padrão glob e quando usar a expressão regular? Obrigado!
Tim
1
@Tim: Globbing está disponível na maioria ou em todas as versões do Bash. A correspondência regex está disponível apenas na versão 3 e superior, mas recomendo usá-la apenas na 3.2 e posterior. Regexes são muito mais versáteis do que globbing.
Pausado até novo aviso.
13
if [[ $gg =~ ^....grid.* ]]
Ignacio Vazquez-Abrams
fonte
1
Você deve ser capaz de usar ". {4}" em vez de "....", ou seja, "^. {4} grade. *". Pode ser mais fácil de ler e entender.
user276648
7

Adicionando esta solução com grepe shbuiltins básicos para aqueles interessados ​​em uma solução mais portátil (independente da bashversão; também funciona com o antigo sh, em plataformas não Linux etc.)

# GLOB matching
gg=svm-grid-ch    
case "$gg" in
   *grid*) echo $gg ;;
esac

# REGEXP    
if echo "$gg" | grep '^....grid*' >/dev/null ; then echo $gg ; fi    
if echo "$gg" | grep '....grid*' >/dev/null ; then echo $gg ; fi    
if echo "$gg" | grep 's...grid*' >/dev/null ; then echo $gg ; fi    

# Extended REGEXP
if echo "$gg" | egrep '(^....grid*|....grid*|s...grid*)' >/dev/null ; then
  echo $gg
fi    

Algumas grepencarnações também suportam a -qopção (silencioso) como alternativa ao redirecionamento para /dev/null, mas o redirecionamento é novamente o mais portátil.

vladr
fonte
esqueci um fechamento ")" para egrep
ghostdog74
5
Use em grep -qvez de grep >/dev/null.
bfontaine
3

@OP,

Glob pettern não é usado apenas para nomes de arquivos?

Não, o padrão "glob" não é usado apenas para nomes de arquivos. você também pode usá-lo para comparar strings. Em seus exemplos, você pode usar case / esac para procurar padrões de strings.

 gg=svm-grid-ch 
 # looking for the word "grid" in the string $gg
 case "$gg" in
    *grid* ) echo "found";;
 esac

 # [[ $gg =~ ^....grid* ]]
 case "$gg" in ????grid*) echo "found";; esac 

 # [[ $gg =~ s...grid* ]]
 case "$gg" in s???grid*) echo "found";; esac

No bash, quando usar o padrão glob e quando usar a expressão regular? Obrigado!

Regex são mais versáteis e "convenientes" do que "padrões glob", entretanto, a menos que você esteja fazendo tarefas complexas que "globbing / extendido" não podem fornecer facilmente, então não há necessidade de usar regex. Regex não tem suporte para a versão do bash <3.2 (como dennis mencionou), mas você ainda pode usar o globbing estendido (por configuração extglob). para globbing estendido, veja aqui e alguns exemplos simples aqui .

Atualização para OP: exemplo para localizar arquivos que começam com 2 caracteres (os pontos "." Significa 1 caractere) seguido por "g" usando regex

por exemplo, saída

$ shopt -s dotglob
$ ls -1 *
abg
degree
..g

$ for file in *; do [[ $file =~ "..g" ]] && echo $file ; done
abg
degree
..g

Acima, os arquivos são correspondidos porque seus nomes contêm 2 caracteres seguidos de "g". (ou seja ..g).

O equivalente com globbing será algo assim: (veja a referência para o significado de ?e *)

$ for file in ??g*; do echo $file; done
abg
degree
..g
ghostdog74
fonte
Obrigado ghostdog74. No Bash com versão superior a 3.2, a expressão regular pode ser usada para substituir o padrão glob sempre que este aparecer? Ou a expressão regular só pode ser usada em algumas circunstâncias especiais? Por exemplo, descobri que "ls ?? g" está funcionando enquanto "ls ..g" não está.
Tim
Não há como impedi-lo de usar regex se for necessário. Você decide. Observe que a sintaxe regex é diferente da sintaxe global do shell. então ls ..gnão funciona. Você está dizendo ao shell para procurar um arquivo com nome ..g. Quanto a aprender sobre a sintaxe regex, você pode tentar perldoc perlretut,perldoc perlrequick ou fazer um info sedna linha de comando.
ghostdog74