Como posso combinar uma string com uma expressão regular no Bash?

166

Estou tentando escrever um script que contém uma função assim quando dado um .tar, .tar.bz2, .tar.gzarquivo etc. ele usa tar com os interruptores relevantes para descompactar o arquivo.

Estou usando as instruções if elif then que testam o nome do arquivo para ver com o que ele termina e não consigo fazer a correspondência usando os metacaracteres regex.

Para economizar constantemente reescrevendo o script que estou usando 'test' na linha de comando, pensei que a instrução abaixo deveria funcionar, tentei todas as combinações de colchetes, aspas e metacaracteres possíveis e, ainda assim, falha.

test sed-4.2.2.tar.bz2 = tar\.bz2$; echo $?
(this returns 1, false)

Tenho certeza de que o problema é simples e procurei em todos os lugares, mas não consigo entender como fazê-lo. Alguém sabe como eu posso fazer isso?

user1587462
fonte

Respostas:

268

Para corresponder às expressões regulares, você precisa usar o =~operador.

Tente o seguinte:

[[ sed-4.2.2.tar.bz2 =~ tar.bz2$ ]] && echo matched

Como alternativa, você pode usar caracteres curinga (em vez de expressões regulares) com o ==operador:

[[ sed-4.2.2.tar.bz2 == *tar.bz2 ]] && echo matched

Se a portabilidade não for uma preocupação, recomendo usar em [[vez de [ou testcomo é mais seguro e mais poderoso. Consulte Qual é a diferença entre test, [e [[? para detalhes.

dogbane
fonte
7
Cuidado com a correspondência de curinga global no segundo exemplo. Dentro de [[]], o * não é expandido como costuma ser, para corresponder aos nomes de arquivos no diretório atual que correspondem a um padrão. Seu exemplo funciona, mas é muito fácil generalizar demais e acreditar erroneamente que * significa corresponder a qualquer coisa em qualquer contexto. Só funciona assim dentro de [[]]. Caso contrário, ele se expandirá para os nomes de arquivos existentes.
Alan Porter
7
Eu tentei usar aspas no regex e falhei; esta resposta ajudou a fazer esse trabalho check="^a.*c$";if [[ "abc" =~ $check ]];then echo match;fi, precisamos armazenar o regex em um var
Poder de Aquário
Observe também que o regexp (como em perl) NÃO deve estar entre parênteses: [[ sed-4.2.2.tar.bz2 == "*tar.bz2" ]]não funcionaria.
pevik 27/02
18
FWIW, a sintaxe para negação (ou seja , não corresponde ) é [[ ! foo =~ bar ]].
Skippy le Grand Gourou
1
dash não suporta o -n 1parâmetro, nem o coloca automaticamente em uma $REPLYvariável. Cuidado!
54

Uma função para fazer isso

extract () {
  if [ -f $1 ] ; then
      case $1 in
          *.tar.bz2)   tar xvjf $1    ;;
          *.tar.gz)    tar xvzf $1    ;;
          *.bz2)       bunzip2 $1     ;;
          *.rar)       rar x $1       ;;
          *.gz)        gunzip $1      ;;
          *.tar)       tar xvf $1     ;;
          *.tbz2)      tar xvjf $1    ;;
          *.tgz)       tar xvzf $1    ;;
          *.zip)       unzip $1       ;;
          *.Z)         uncompress $1  ;;
          *.7z)        7z x $1        ;;
          *)           echo "don't know '$1'..." ;;
      esac
  else
      echo "'$1' is not a valid file!"
  fi
}

Outra nota

Em resposta ao Aquarius Power no comentário acima, We need to store the regex on a var

A variável BASH_REMATCH é definida após a correspondência da expressão, e $ {BASH_REMATCH [n]} corresponderá ao enésimo grupo entre parênteses, ou seja, a seguir ${BASH_REMATCH[1]} = "compressed"e${BASH_REMATCH[2]} = ".gz"

if [[ "compressed.gz" =~ ^(.*)(\.[a-z]{1,5})$ ]]; 
then 
  echo ${BASH_REMATCH[2]} ; 
else 
  echo "Not proper format"; 
fi

(O regex acima não deve ser válido para nomes e extensões de arquivos, mas funciona como exemplo)

dualidade
fonte
Observe também que, com o BSD tar, você pode usar "tar xf" para todos os formatos e não precisa de comandos separados ou dessa função.
Boa Pessoa
ano GNU tar ou pno BSD tar para dizer explicitamente para inferir automaticamente o tipo de compactação da extensão. O GNU tar não fará isso automaticamente caso contrário, e acho que pelo comentário da @GoodPerson que o BSD tar faz isso por padrão.
Mark K Cowan
7z pode descompactar .. AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS , UDF, UEFI, VDI, VHD, VMDK, WIM, XAR e Z. consulte 7-zip.org
mosh
14

Não tenho representante suficiente para comentar aqui, por isso estou enviando uma nova resposta para melhorar a resposta da dogbane. O ponto . no regexp

[[ sed-4.2.2.tar.bz2 =~ tar.bz2$ ]] && echo matched

na verdade corresponderá a qualquer caractere, não apenas ao ponto literal entre 'tar.bz2', por exemplo

[[ sed-4.2.2.tar4bz2 =~ tar.bz2$ ]] && echo matched
[[ sed-4.2.2.tar§bz2 =~ tar.bz2$ ]] && echo matched

ou qualquer coisa que não exija escape com '\'. A sintaxe estrita deve então ser

[[ sed-4.2.2.tar.bz2 =~ tar\.bz2$ ]] && echo matched

ou você pode ser ainda mais rigoroso e também incluir o ponto anterior na regex:

[[ sed-4.2.2.tar.bz2 =~ \.tar\.bz2$ ]] && echo matched
user2066480
fonte
9

Como você está usando o bash, não é necessário criar um processo filho para fazer isso. Aqui está uma solução que a executa inteiramente no bash:

[[ $TEST =~ ^(.*):\ +(.*)$ ]] && TEST=${BASH_REMATCH[1]}:${BASH_REMATCH[2]}

Explicação: Os grupos antes e depois da sequência "dois pontos e um ou mais espaços" são armazenados pelo operador de correspondência de padrões na matriz BASH_REMATCH.

user1934428
fonte
1
Observe que o índice 0 contém a correspondência completa e os índices 1 e 2 contêm as correspondências do grupo.
Rainer Schwarze
3
if [[ $STR == *pattern* ]]
then
    echo "It is the string!"
else
    echo "It's not him!"
fi

Funciona para mim! GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)

juan cortez
fonte
1
Isso é extremamente perigoso; ele se comporta apenas sem um comportamento indefinido para você, porque você não possui arquivos no diretório atual denominado "padrão" da substring literal. Vá em frente, crie alguns arquivos com esse nome e a expansão de substring corresponderá aos arquivos e quebrará tudo horrivelmente com heisenbugs multicoloridos.
I336_
Mas fiz um experimento: com os arquivos `1pattern, pattern pattern2 e pattern no diretório atual. Este script funciona conforme o esperado. Você poderia me fornecer o resultado do teste? @ i336_
juan cortez
2
@ i336: Acho que não. Dentro [[ ... ]], o padrão rhs glob não se expande de acordo com o diretório atual, como normalmente faria.
user1934428
@ i336_ Não. Dentro [[...]], o Bash não realiza a expansão do nome do arquivo. No manual do bash,Word splitting and filename expansion are not performed on the words between the [[ and ]];
jinbeom hong 02/07
@jinbeomhong: TIL. É bom saber disso, obrigado!
i336_ 02/07
2

shopt -s nocasematch

if [[ sed-4.2.2.$LINE =~ (yes|y)$ ]]
 then exit 0 
fi
Shyam Gupta
fonte