Comment passer une expression régulière lors de la recherche d'un chemin de répertoire dans bash?

14

J'ai écrit un petit script bash pour trouver un répertoire nommé anacondaou minicondadans mon utilisateur $HOME. Mais il ne trouve pas le miniconda2répertoire chez moi.

Comment pourrais-je résoudre ce problème?

if [ -d "$HOME"/"(ana|mini)conda[0-9]?" ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

PS: Si je l'ai [ -d "$HOME"/miniconda2 ]; then, il trouve le répertoire miniconda2, donc je pense que l'erreur réside dans la partie"(ana|mini)conda[0-9]?"

Je veux que le script soit général. Pour moi, c'est miniconda2 mais pour un autre utilisateur, il peut s'agir d'anaconda2, miniconda3 et ainsi de suite.

Jenny
la source
Un autre utilisateur peut utiliser anaconda_2 ou -2 ou -may2019. Alors xxxconda * ne serait-il pas mieux?
WinEunuuchs2Unix
2
L'expansion du nom de fichier Bash utilise des expressions globales, pas des expressions régulières.
Peter Cordes

Réponses:

13

C'est une chose étonnamment délicate à faire correctement.

Fondamentalement, -dne testera qu'un seul argument - même si vous pouvez faire correspondre les noms de fichiers en utilisant une expression régulière.

Une façon serait de retourner le problème et de tester les répertoires pour une correspondance regex au lieu de tester la correspondance regex pour les répertoires. En d'autres termes, parcourez tous les répertoires en $HOMEutilisant un simple glob de shell, et testez chacun contre votre regex, rompant une correspondance, testant enfin si le BASH_REMATCHtableau n'est pas vide:

#!/bin/bash

for d in "$HOME"/*/; do
  if [[ $d =~ (ana|mini)conda[0-9]? ]]; then
    break;
  fi
done

if ((${#BASH_REMATCH[@]} > 0)); then
    echo "anaconda/miniconda directory is found in your $HOME"
  else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Une autre façon serait d'utiliser un glob shell étendu à la place de l'expression régulière, et de capturer toutes les correspondances glob dans un tableau. Testez ensuite si le tableau n'est pas vide:

#!/bin/bash

shopt -s extglob nullglob

dirs=( "$HOME"/@(ana|mini)conda?([0-9])/ )

if (( ${#dirs[@]} > 0 )); then
  echo "anaconda/miniconda directory is found in your $HOME"
else
  echo "anaconda/miniconda is not found in your $HOME"
fi

La fin /garantit que seuls les répertoires correspondent; le nullglobempêche le shell de renvoyer la chaîne sans correspondance dans le cas de correspondances nulles.


Pour rendre l'un ou l'autre récursif, définissez l' globstaroption shell ( shopt -s globstar), puis respectivement: -

  • (version regex): for d in "$HOME"/**/; do

  • (version glob étendue): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )

tournevis
la source
1
J'irais par la voie du tableau. Vous pouvez utiliser ?([0-9])à la place de @(|[0-9])- ?(...)correspond à zéro ou à un, identique au ?quantificateur d' expression régulière.
glenn jackman
2
Vous n'avez même pas besoin d'extglob si vous utilisez l'extension d'accolade (cela génère tous les noms correspondants possibles):~/{ana,mini}conda{0..9}*/
xenoid
Existe-t-il de toute façon de modifier l'une de ces solutions afin qu'elle se maintienne même si miniou anacondaest installé dans $HOME/sub-directories? Par exemple$HOME/sub-dir1/sub-dir2/miniconda2
Jenny
1
@Jenny s'il vous plaît voir ma modification concernantglobstar
steeldriver
1
@terdon ouais je ne voulais pas vraiment aller dans le terrier du lapin de ce qui est la "bonne" chose à faire correspondre - je viens de prendre le regex du PO tel quel dans le but d'illustrer une approche générale
steeldriver
9

En effet, comme déjà mentionné, c'est délicat. Mon approche est la suivante:

  • utiliser findet ses capacités d' expression régulière pour trouver les répertoires en question.
  • laissez findimprimer un xpour chaque répertoire trouvé
  • stocker les xes dans une chaîne
  • si la chaîne n'est pas vide, alors l'un des répertoires a été trouvé.

Donc:

xString=$(find $HOME -maxdepth 1 \
                     -type d \
                     -regextype egrep \
                     -regex "$HOME/(ana|mini)conda[0-9]?" \
                     -printf 'x');
if [ -n "$xString" ]; then
    echo "found one of the directories";
else
    echo "no match.";
fi

Explication:

  • find $HOME -maxdepth 1trouve tout ci-dessous $HOME mais restreint la recherche à un niveau (c'est-à-dire: il ne recuit pas dans les sous-répertoires).
  • -type drestreint la recherche aux seuls drépertoires
  • -regextype egrepindique findquel type d' expression régulière nous traitons. Ceci est nécessaire car des choses comme [0-9]?et (…|…)sont quelque peu spéciales et find ne les reconnaissent pas par défaut.
  • -regex "$HOME/(ana|mini)conda[0-9]?"est l' expression régulière réelle que nous voulons rechercher
  • -printf 'x'imprime juste un xpour chaque chose qui satisfait aux conditions précédentes.
PerlDuck
la source
Quand il y a un match. -bash: -regex: command not found found one of the directories
Jenny
Salut PerlDuck: Merci. Une belle réponse aussi. Mais j'obtiens une erreur pour printfPar exemple lorsque j'exécute le script, il s'exécute correctement mais il ne trouve pas la commande printf lorsqu'il n'y a pas de correspondance mais je pense que c'est parce qu'il n'y a rien à imprimer peut-être?. -bash: -printf: command not found no match.
Jenny
3
@Jenny Vous avez peut-être fait une faute de frappe lors de la copie, car cela fonctionne bien pour moi. -printfn'est pas une commande mais un argument pour find. C'est ce que fait la barre oblique inversée à la fin de la ligne précédente.
wjandrea
1
Je suggérerais -quitaprès avoir imprimé le chemin trouvé, sauf si vous voulez continuer à détecter l'ambiguïté.
Peter Cordes
Et pourquoi ne pas imprimer le chemin réel? Vous l'avez déjà, il semble donc dommage de le jeter et de l'utiliser à la xplace:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon
2

Vous pouvez parcourir une liste de noms de répertoires que vous souhaitez tester et agir dessus si l'un d'eux existe:

a=0
for i in {ana,mini}conda{,2}; do
  if [ -d "$i" ]; then
    unset a
    break
  fi
done
echo "anaconda/miniconda directory is ${a+not }found in your $HOME"

Cette solution ne permet évidemment pas la pleine puissance des regex, mais l'expansion de la coque et de l'accolade est au moins égale dans le cas que vous avez montré. La boucle se ferme dès qu'un répertoire existe et désactive la variable précédemment définie a. Dans la echoligne suivante , l' expansion des paramètres se ${a+not } développe à rien si aest définie (= aucun répertoire trouvé) et «pas» autrement.

dessert
la source
1

La solution possible consiste à rechercher séparément miniconda et anaconda comme indiqué ci-dessous

if [ -d "$HOME"/miniconda* ] || [ -d "$HOME"/anaconda* ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Mais si quelqu'un a des suggestions, j'aimerais savoir pourquoi nous ne pouvons pas passer une expression régulière lors de la recherche de répertoires.

Jenny
la source
2
J'ai voté pour cela - mais je me suis alors rendu compte qu'il se briserait si l'utilisateur avait plus d'un répertoire correspondant (par exemple miniconda ET miniconda2)
steeldriver
@steeldriver: "il se cassera si l'utilisateur a plus d'un répertoire correspondant" Oui, c'est en effet vrai. Avez-vous des suggestions pour y remédier?
Jenny
@Jenny Utilisez un tableau, comme dans la réponse de Steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea
Si vous le remplacez ] || [par -oau moins ne devrait pas casser si les deux répertoires sont trouvés car les deux globes de répertoire sont recherchés dans le même test.
Phoenix
@steeldriver et Jenny: vous voudrez peut - être briser l'ambiguïté au lieu d'en choisir une. Demandez à l'utilisateur de spécifier son répertoire au lieu de choisir peut-être le mauvais. (par exemple, modifiez le script pour définir le nom du répertoire au lieu d'exécuter le code de détection automatique.)
Peter Cordes