Comment puis-je faire correspondre une chaîne avec une expression régulière dans Bash?

166

Je suis en train d'écrire un script bash qui contient une fonction alors quand donné .tar, .tar.bz2, .tar.gzetc. fichier , il utilise le goudron avec les commutateurs concernés pour décompresser le fichier.

J'utilise if elif then des déclarations qui testent le nom de fichier pour voir par quoi il se termine et je ne peux pas le faire correspondre en utilisant des métacaractères regex.

Pour éviter de réécrire constamment le script que j'utilise `` test '' sur la ligne de commande, j'ai pensé que la déclaration ci-dessous devrait fonctionner, j'ai essayé toutes les combinaisons de crochets, de guillemets et de méta-caractères possibles et cela échoue toujours.

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

Je suis sûr que le problème est simple et j'ai regardé partout, mais je ne sais pas comment le faire. Est-ce que quelqu'un sait comment je peux faire ça?

user1587462
la source

Réponses:

268

Pour faire correspondre les expressions régulières, vous devez utiliser l' =~opérateur.

Essaye ça:

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

Vous pouvez également utiliser des caractères génériques (au lieu des expressions régulières) avec l' ==opérateur:

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

Si la portabilité n'est pas un problème, je recommande d'utiliser à la [[place [ou testcar il est plus sûr et plus puissant. Voir Quelle est la différence entre test, [et [[? pour plus de détails.

dogbane
la source
7
Soyez prudent avec la correspondance générique glob dans le deuxième exemple. À l'intérieur de [[]], le * n'est pas développé comme d'habitude, pour correspondre aux noms de fichiers du répertoire courant qui correspondent à un modèle. Votre exemple fonctionne, mais il est vraiment facile de trop généraliser et de croire à tort que * signifie faire correspondre tout contexte. Cela ne fonctionne que comme ça à l'intérieur de [[]]. Sinon, il s'étend aux noms de fichiers existants.
Alan Porter le
7
J'ai essayé d'utiliser des guillemets sur l'expression régulière et j'ai échoué; cette réponse a aidé à faire ce travail, check="^a.*c$";if [[ "abc" =~ $check ]];then echo match;finous devons stocker le regex sur un var
Aquarius Power
Notez également que l'expression rationnelle (comme en perl) ne doit PAS être entre parenthèses: [[ sed-4.2.2.tar.bz2 == "*tar.bz2" ]]ne fonctionnerait pas.
pevik
18
FWIW, la syntaxe de la négation (c'est -à- dire ne correspond pas ) est [[ ! foo =~ bar ]].
Skippy le Grand Gourou
1
dash ne prend pas en charge le -n 1paramètre, ni ne le met automatiquement dans une $REPLYvariable. Fais attention!
54

Une fonction pour faire ceci

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
}

Autre note

En réponse à Aquarius Power dans le commentaire ci-dessus, We need to store the regex on a var

La variable BASH_REMATCH est définie une fois que vous avez fait correspondre l'expression, et $ {BASH_REMATCH [n]} correspondra au nième groupe entouré de parenthèses, c'est-à-dire dans ce qui suit ${BASH_REMATCH[1]} = "compressed"et${BASH_REMATCH[2]} = ".gz"

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

(Le regex ci-dessus n'est pas censé être valide pour la dénomination et les extensions de fichier, mais cela fonctionne pour l'exemple)

dualité
la source
notez également qu'avec BSD tar, vous pouvez utiliser "tar xf" pour tous les formats et vous n'avez pas besoin de commandes séparées ou de cette fonction.
Good Person
asur GNU tar ou psur BSD tar pour lui dire explicitement de déduire automatiquement le type de compression à partir de l'extension. GNU tar ne le fera pas automatiquement autrement, et je suppose d'après le commentaire de @GoodPerson que BSD tar le fait par défaut.
Mark K Cowan
7z peut décompresser .. 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 et Z. voir 7-zip.org
mosh
14

Je n'ai pas assez de représentants pour commenter ici, alors je soumets une nouvelle réponse pour améliorer la réponse de dogbane. Le point . dans l'expression rationnelle

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

correspondra en fait à n'importe quel caractère, pas seulement au point littéral entre 'tar.bz2', par exemple

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

ou tout ce qui ne nécessite pas de s'échapper avec '\'. La syntaxe stricte devrait alors être

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

ou vous pouvez aller encore plus stricte et inclure également le point précédent dans l'expression régulière:

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

Puisque vous utilisez bash, vous n'avez pas besoin de créer un processus enfant pour ce faire. Voici une solution qui l'exécute entièrement dans bash:

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

Explication: Les groupes avant et après la séquence "deux-points et un ou plusieurs espaces" sont stockés par l'opérateur de correspondance de modèle dans le tableau BASH_REMATCH.

user1934428
la source
1
Notez que l'index 0 contient la correspondance complète et les index 1 et 2 contiennent les correspondances de groupe.
Rainer Schwarze
3
if [[ $STR == *pattern* ]]
then
    echo "It is the string!"
else
    echo "It's not him!"
fi

Travaille pour moi! GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)

Juan Cortez
la source
1
C'est extrêmement dangereux; il se comporte uniquement sans comportement indéfini pour vous car vous n'avez aucun fichier dans le répertoire courant nommé la sous-chaîne littérale "pattern". Allez-y, créez des fichiers nommés comme ça, et l'expansion des sous-chaînes correspondra aux fichiers et cassera tout horriblement avec des heisenbugs multicolores.
i336_
Mais j'ai fait une expérience: avec les fichiers `1pattern, pattern pattern2 et pattern dans le répertoire courant. Ce script fonctionne comme prévu. Pouvez-vous me fournir le résultat de votre test? @ i336_
juan cortez
2
@ i336: Je ne pense pas. À l'intérieur [[ ... ]], le modèle rhs glob ne se développe pas en fonction du répertoire courant, comme il le ferait habituellement.
user1934428
@ i336_ Non. A l'intérieur [[...]], Bash n'effectue pas de développement de nom de fichier. Dans le manuel bash,Word splitting and filename expansion are not performed on the words between the [[ and ]];
jinbeom hong le
@jinbeomhong: TIL. C'est bon à savoir, merci!
i336_ le
2

shopt -s nocasematch

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