Come passare un regex quando si trova un percorso di directory in bash?


14

Ho scritto un piccolo script bash per scoprire se una directory si chiama anacondao minicondanel mio utente $HOME. Ma non trova la miniconda2directory nella mia casa.

Come potrei risolvere questo?

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: Se ce l'ho [ -d "$HOME"/miniconda2 ]; then, trova la directory miniconda2, quindi penso che l'errore risieda nella parte"(ana|mini)conda[0-9]?"

Voglio che la sceneggiatura sia generale. Per me è miniconda2 ma per qualche altro utente potrebbe essere anaconda2, miniconda3 e così via.


Un altro utente potrebbe utilizzare anaconda_2 o -2 o -may2019. Quindi xxxconda * non sarebbe migliore?
WinEunuuchs2Unix

2
L'espansione del nome file di Bash utilizza espressioni glob, non regex.
Peter Cordes,

Risposte:


13

Questa è una cosa sorprendentemente complicata da fare bene.

Fondamentalmente, -dmetterà alla prova solo un singolo argomento, anche se potresti abbinare i nomi di file usando un'espressione regolare.

Un modo sarebbe quello di capovolgere il problema e testare le directory per una corrispondenza regex invece di testare la corrispondenza regex per le directory. In altre parole, passa in rassegna tutte le directory $HOMEusando un semplice shell glob, e testa ciascuna contro la tua regex, interrompendo una corrispondenza, e infine verificando se l' BASH_REMATCHarray non è vuoto:

#!/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

Un modo alternativo sarebbe quello di utilizzare un glob shell esteso al posto del regex e catturare qualsiasi corrispondenza glob in un array. Quindi verificare se l'array non è vuoto:

#!/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

Il trailing /assicura che siano abbinate solo le directory; la nullglobimpedisce alla shell di ritorno stringa uguali nel caso di zero abbinamento.


Per rendere ricorsivo, impostare l' globstaropzione di shell ( shopt -s globstar) e quindi rispettivamente: -

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

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


1
Vorrei seguire la rotta dell'array. È possibile utilizzare ?([0-9])al posto di @(|[0-9])- ?(...)corrisponde a zero o uno, lo stesso del ?quantificatore regex .
Glenn Jackman,

2
Non hai nemmeno bisogno di extglob se usi l'espansione parentesi (questo genera tutti i possibili nomi corrispondenti):~/{ana,mini}conda{0..9}*/
xenoid

Esiste un modo per modificare una di queste soluzioni in modo che rimarrà valido anche se minio anacondaè installato $HOME/sub-directories? Ad esempio$HOME/sub-dir1/sub-dir2/miniconda2
Jenny

1
@Jenny, per favore, vedi la mia modifica riguardanteglobstar
steeldriver,

1
@terdon sì, non volevo davvero andare nella tana del coniglio di quale fosse la cosa "giusta" da abbinare - ho appena preso il regex dell'OP così com'è allo scopo di illustrare un approccio generale
steeldriver

9

In effetti, come già accennato, questo è difficile. Il mio approccio è il seguente:

  • utilizzare finde le sue funzionalità regex per trovare le directory in questione.
  • lasciate findstampare un xper ogni directory trovata
  • memorizza gli xes in una stringa
  • se la stringa non è vuota, è stata trovata una delle directory.

Così:

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

Spiegazione:

  • find $HOME -maxdepth 1trova tutto sotto $HOME ma limita la ricerca a un livello (ovvero: non ricorre nelle sottodirectory).
  • -type dlimita la ricerca alle sole dirtorie
  • -regextype egrepindica con findquale tipo di espressione regolare abbiamo a che fare. Ciò è necessario perché le cose piacciono [0-9]?e (…|…)sono in qualche modo speciali e find non le riconoscono per impostazione predefinita.
  • -regex "$HOME/(ana|mini)conda[0-9]?"è l' espressione regolare effettiva che vogliamo cercare
  • -printf 'x'stampa solo una xper ogni cosa che soddisfa le condizioni precedenti.

Quando c'è una partita. -bash: -regex: command not found found one of the directories
Jenny

Ciao PerlDuck: grazie. Anche una bella risposta. Ma ricevo un errore per printfesempio quando eseguo lo script, funziona bene ma non trova il comando printf quando non c'è corrispondenza ma penso che sia perché non c'è niente da stampare potrebbe essere ?. -bash: -printf: command not found no match.
Jenny,

3
@Jenny Potresti aver fatto un refuso durante la copia, dal momento che funziona bene per me. -printfnon è un comando ma un argomento per find. Questo è ciò che fa la barra rovesciata alla fine della riga precedente.
wjandrea,

1
Suggerirei -quitdopo aver stampato il percorso trovato, a meno che tu non voglia continuare a rilevare l'ambiguità.
Peter Cordes,

E perché non stampare il percorso effettivo? Lo hai già, quindi sembra un peccato scartarlo e usarlo xinvece:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon

2

Puoi passare in rassegna un elenco di nomi di directory che desideri testare e agire su di esso se esiste uno di essi:

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"

Questa soluzione ovviamente non consente la piena potenza regex, ma l'espulsione del guscio e l'espansione del rinforzo sono uguali almeno nel caso mostrato. Il ciclo termina non appena esiste una directory e annulla la variabile precedentemente impostata a. Nella echoriga successiva , l' espansione del parametro si${a+not } espande in nulla se aè impostato (= nessuna directory trovata) e “no” altrimenti.


1

Possibile soluzione è la ricerca di miniconda e anaconda separatamente come mostrato di seguito

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

Ma se qualcuno ha suggerimenti, mi piacerebbe sapere perché non possiamo passare una regex durante la ricerca di directory.


2
Ho votato a favore - ma poi ho capito che si romperà se l'utente ha più di una directory corrispondente (ad es. Miniconda E miniconda2)
steeldriver

@steeldriver: "si interromperà se l'utente ha più di una directory corrispondente" Sì, è vero. Hai qualche suggerimento su come risolverlo?
Jenny,

@Jenny Usa un array, come nella risposta di Steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea,

Se si sostituisce ] || [con -oesso, almeno non dovrebbe rompersi se si trovano entrambe le directory poiché entrambi i globi di directory vengono cercati nello stesso test.
Phoenix,

@steeldriver e Jenny: potresti voler interrompere l'ambiguità invece di sceglierne solo uno. Fai in modo che l'utente specifichi la propria directory invece di scegliere quella sbagliata. (ad esempio, modifica lo script per impostare il nome della directory anziché eseguire il codice di rilevamento automatico.)
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.