Jak przekazać wyrażenie regularne podczas wyszukiwania ścieżki katalogu w bash?

14

Napisałem mały skrypt bash, aby sprawdzić, czy katalog ma nazwę anacondaczy minicondaw moim użytkowniku $HOME. Ale nie znajduje miniconda2katalogu w moim domu.

Jak mogę to naprawić?

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: Jeśli tak [ -d "$HOME"/miniconda2 ]; then, to znajduje katalog miniconda2, więc myślę, że w tym leży błąd"(ana|mini)conda[0-9]?"

Chcę, aby skrypt był ogólny. Dla mnie jest to miniconda2, ale dla innego użytkownika może to być anaconda2, miniconda3 i tak dalej.

Przędzarka
źródło
Inny użytkownik może użyć anaconda_2 lub -2 lub -may2019. Czy xxxconda * nie byłaby lepsza?
WinEunuuchs2Unix
2
Rozszerzenie nazwy pliku Bash używa wyrażeń globalnych, a nie wyrażeń regularnych.
Peter Cordes

Odpowiedzi:

13

Jest to zaskakująco trudna rzecz do zrobienia.

Zasadniczo -dprzetestuje tylko jeden argument - nawet jeśli można dopasować nazwy plików za pomocą wyrażeń regularnych.

Jednym ze sposobów byłoby odwrócenie problemu i przetestowanie katalogów pod kątem dopasowania wyrażenia regularnego zamiast testowania dopasowania wyrażenia regularnego dla katalogów. Innymi słowy, przeglądaj wszystkie katalogi $HOMEza pomocą prostego globu powłoki i testuj każdy z wyrażeniem regularnym, przerywając dopasowanie, w końcu sprawdzając, czy BASH_REMATCHtablica nie jest pusta:

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

Alternatywnym sposobem byłoby użycie rozszerzonego globu powłoki zamiast wyrażenia regularnego i przechwytywanie dowolnych dopasowań globów w tablicy. Następnie sprawdź, czy tablica nie jest pusta:

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

Końcowe /zapewnia dopasowanie tylko katalogów; nullglobzabezpiecza powłokę przed powrotem niedopasowanej ciąg w przypadku zerowej meczów.


Aby ustawić rekurencję, ustaw globstaropcję powłoki ( shopt -s globstar), a następnie odpowiednio:

  • (wersja regularna): for d in "$HOME"/**/; do

  • (rozszerzona wersja globalna): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )

steeldriver
źródło
1
Wybrałbym trasę tablicy. Możesz użyć ?([0-9])zamiast @(|[0-9])- ?(...)dopasowuje zero lub jeden, tak samo jak ?kwantyfikator wyrażenia regularnego .
glenn jackman
2
Nie potrzebujesz nawet extglob, jeśli używasz rozwijania nawiasów klamrowych (generuje to wszystkie możliwe pasujące nazwy):~/{ana,mini}conda{0..9}*/
xenoid
Czy mimo to edycja jednej z tych rozwiązań tak, że będzie trzymać nawet jeśli minilub anacondajest zainstalowana w $HOME/sub-directories? Na przykład$HOME/sub-dir1/sub-dir2/miniconda2
Jenny
1
@Jenny, proszę zobaczyć moją edycję dotyczącąglobstar
steeldriver
1
@terdon tak, tak naprawdę nie chciałem zejść do króliczej nory tego, co jest „właściwe”, aby dopasować - właśnie wziąłem regex OP w obecnej postaci w celu zilustrowania ogólnego podejścia
steeldriver
9

Rzeczywiście, jak już wspomniano, jest to trudne. Moje podejście jest następujące:

  • use findi jego wyrażenia regularne do znajdowania danych katalogów.
  • niech findwypisuje xdla każdego znalezionego katalogu
  • przechowuj xes w ciągu
  • jeśli ciąg nie jest pusty, to znaleziono jeden z katalogów.

A zatem:

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

Wyjaśnienie:

  • find $HOME -maxdepth 1znajduje wszystko poniżej, $HOME ale ogranicza wyszukiwanie do jednego poziomu (to znaczy: nie powraca do podkatalogów).
  • -type dogranicza wyszukiwanie tylko do directories
  • -regextype egrepmówi, z findjakim rodzajem wyrażenia regularnego mamy do czynienia. Jest to potrzebne, ponieważ rzeczy takie jak [0-9]?i (…|…)są nieco wyjątkowe i find domyślnie ich nie rozpoznają.
  • -regex "$HOME/(ana|mini)conda[0-9]?"jest właściwym wyrażeniem regularnym, którego chcemy szukać
  • -printf 'x'po prostu drukuje xdla każdej rzeczy, która spełnia poprzednie warunki.
PerlDuck
źródło
Kiedy jest dopasowanie. -bash: -regex: command not found found one of the directories
Jenny
Cześć PerlDuck: Dzięki. Miła odpowiedź. Ale printfpojawia się błąd na przykład, kiedy uruchamiam skrypt, działa on dobrze, ale nie znajduje polecenia printf, gdy nie ma dopasowania, ale myślę, że dzieje się tak, ponieważ może nie być nic do wydrukowania ?. -bash: -printf: command not found no match.
Jenny
3
@Jenny Być może podczas kopiowania popełniłeś literówkę, ponieważ działa dla mnie dobrze. -printfnie jest poleceniem, ale argumentem na find. To właśnie robi odwrotny ukośnik na końcu poprzedniej linii.
wjandrea
1
Sugeruję -quitpo wydrukowaniu znalezionej ścieżki, chyba że chcesz nadal wykrywać dwuznaczności.
Peter Cordes
A dlaczego nie wydrukować rzeczywistej ścieżki? Masz go już, więc wstydem jest go odrzucić i xzamiast tego użyć :foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon
2

Możesz przewinąć listę nazw katalogów, które chcesz przetestować, i wykonać na niej działania, jeśli jedna z nich istnieje:

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"

To rozwiązanie oczywiście nie pozwala na pełną moc wyrażania regularnego, ale globowanie powłoki i rozwijanie nawiasów jest równe przynajmniej w pokazanym przypadku. Pętla kończy się, gdy tylko istnieje jeden katalog i resetuje poprzednio ustawioną zmienną a. W kolejnym echowierszu rozszerzenie parametru ${a+not } rozwija się do zera, jeśli ajest ustawione (= nie znaleziono katalogu ) i „nie”.

deser
źródło
1

Możliwym obejściem jest osobne przeszukiwanie minikondy i anakondy, jak pokazano poniżej

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

Ale jeśli ktoś ma sugestie, chciałbym wiedzieć, dlaczego nie możemy przekazać wyrażenia regularnego podczas wyszukiwania katalogów.

Przędzarka
źródło
2
Głosowałem za tym - ale potem zdałem sobie sprawę, że się zepsuje, jeśli użytkownik ma więcej niż jeden pasujący katalog (np. Miniconda ORAZ miniconda2)
steeldriver
@steeldriver: „pęknie, jeśli użytkownik ma więcej niż jeden pasujący katalog” Tak, to prawda. Czy masz jakieś sugestie, jak to naprawić?
Jenny
@Jenny Użyj tablicy, jak w odpowiedzi steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea
W przypadku wymiany ] || [z -onim przynajmniej nie powinny pęknąć, jeśli znajdują się zarówno katalogi jak oba globs katalogowych są spojrzał w tym samym teście.
Phoenix
@steeldriver i Jenny: możesz chcieć, aby to złamało się na dwuznaczności zamiast po prostu wybrać jedną. Niech użytkownik określi swój katalog zamiast wybierać niewłaściwy. (np. edytuj skrypt, aby ustawić nazwę katalogu zamiast uruchamiać kod automatycznego wykrywania.)
Peter Cordes