Як пройти регулярний вираз при пошуку шляху до каталогу в bash?


14

Я написав невеликий скрипт bash, щоб дізнатися, чи є ім’я в каталозі anacondaабо minicondaу мого користувача $HOME. Але він не знаходить miniconda2довідника в моєму домі.

Як я можу це виправити?

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: Якщо у мене є [ -d "$HOME"/miniconda2 ]; then, то він знаходить каталог miniconda2, тому я думаю, що в цій частині лежить помилка"(ana|mini)conda[0-9]?"

Я хочу, щоб сценарій був загальним. Для мене це miniconda2, але для іншого користувача це може бути anaconda2, miniconda3 тощо.


Інший користувач може використовувати anaconda_2 або -2 або -may2019. То хіба ххконконда * не буде кращою?
WinEunuuchs2Unix

2
Розширення назви файлів Bash використовує глобальні вирази, а не регулярні вирази.
Пітер Кордес

Відповіді:


13

Це напрочуд хитра справа зробити красиво.

По суті, -dбуде протестовано лише один аргумент - навіть якщо ви могли зіставити назви файлів, використовуючи регулярний вираз.

Одним із способів було б перевернути проблему і протестувати каталоги на відповідність регулярних виразів замість тестування збігу регулярних виразів для каталогів. Іншими словами, переведіть цикл на всі каталоги, $HOMEвикористовуючи просту оболонку оболонки, і протестуйте кожен на свій регулярний вираз, перериваючись на збіг, остаточно перевіряючи, чи BASH_REMATCHмасив не порожній:

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

Альтернативним способом було б використання розширеного глобула оболонки замість регулярного вираження та захоплення будь-яких матчів з глобусом у масиві. Потім перевірити, чи масив не порожній:

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

Простеження /забезпечує узгодження лише каталогів; nullglobзапобігає шкаралупу від повернення неперевершеною рядки в разі нульових матчів.


Щоб зробити будь-який рекурсивний, встановіть параметр globstarоболонки ( shopt -s globstar), а потім відповідно: -

  • (версія регулярного вираження): for d in "$HOME"/**/; do

  • (розширена глобальна версія): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )


1
Я б пішов шляхом масиву. Ви можете використовувати ?([0-9])замість @(|[0-9])- ?(...)відповідає нулю або одному, такому ж, як ?кількісний показник регулярних виразів.
Гленн Джекман

2
Вам навіть не потрібен extglob, якщо ви використовуєте розширення дужок (це генерує всі можливі відповідні імена):~/{ana,mini}conda{0..9}*/
xenoid

Є в будь-якому випадку для редагування або з цих рішень , так що він буде тримати навіть якщо miniі anacondaвстановлений в $HOME/sub-directories? Наприклад$HOME/sub-dir1/sub-dir2/miniconda2
Дженні

1
@Jenny, будь ласка, дивіться мою globstar
редакцію

1
@terdon Так, я не дуже хотів спускатися з кролячої нори того, що "правильне", щоб відповідати - я просто взяв
регекс

9

Дійсно, як уже говорилося, це хитро. Мій підхід такий:

  • використання findта його можливості регулярного вираження для пошуку відповідних каталогів.
  • дозвольте findнадрукувати xдля кожного знайденого каталогу
  • збережіть xes у рядку
  • якщо рядок не порожній, то знайдено один із каталогів.

Таким чином:

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

Пояснення:

  • find $HOME -maxdepth 1знаходить усе нижче, $HOME але обмежує пошук до одного рівня (тобто: не повторюється в підкаталогах).
  • -type dобмежує пошук лише dіректоріями
  • -regextype egrepрозповідає, з findяким типом регулярного вираження ми маємо справу. Це потрібно, оскільки речі подобаються [0-9]?та (…|…)є дещо особливими, і find вони не розпізнають їх за замовчуванням.
  • -regex "$HOME/(ana|mini)conda[0-9]?"- це власне регулярний вираз, на який ми хочемо оглянути
  • -printf 'x'просто роздруковує xдля кожної речі, яка відповідає попереднім умовам.

Коли є сірник. -bash: -regex: command not found found one of the directories
Дженні

Привіт PerlDuck: Дякую Приємна відповідь теж. Але я отримую помилку, printfнаприклад, коли я запускаю скрипт, він працює нормально, але він не знаходить команду printf, коли немає відповідності, але я думаю, це тому, що друкувати нічого не може. -bash: -printf: command not found no match.
Дженні

3
@Jenny Ви, можливо, зробили друкарську помилку під час її копіювання, оскільки це добре працює для мене. -printf- це не команда, а аргумент find. Ось що робить зворотний нахил в кінці попереднього рядка.
wjandrea

1
Я б запропонував -quitпісля друку знайденого шляху, якщо ви не хочете продовжувати виявляти неоднозначність.
Пітер Кордес

А чому б не надрукувати фактичний шлях? У вас це вже є, тому здається прикро відмовитися від нього та використовувати xнатомість:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon

2

Ви можете перевести цикл на список імен каталогів, які ви хочете перевірити, і діяти на нього, якщо одне з них існує:

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"

Це рішення, очевидно, не дозволяє отримати повну потужність зворотного випромінювання, але глобалізація оболонок та розширення брекетів є рівною щонайменше у випадку, що ви показали. Цикл виходить, як тільки існує один каталог, і скасовує раніше встановлену змінну a. У наступному echoрядку розширення параметра ${a+not } розширюється до нічого, якщо aвстановлено (= немає dir знайдено) і "не" в іншому.


1

Можлива робота навколо пошуку мініконди та анаконди окремо, як показано нижче

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

Але якщо у когось є пропозиції, я хотів би знати, чому ми не можемо пройти регулярний вираз під час пошуку каталогів.


2
Я підтримав це, але потім зрозумів, що воно порушиться, якщо користувач матиме більше одного відповідного каталогу (наприклад, miniconda AND miniconda2)
steeldriver

@steeldriver: "це зламається, якщо у користувача буде більше однієї відповідної каталоги" Так, це дійсно так. Чи є якісь пропозиції, як це виправити?
Дженні

@Jenny Використовуйте масив, як у відповіді steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea

Якщо замінити ] || [з -oним , по крайней мере не повинні зламатися , якщо обидва каталогу знаходяться в обох кульок каталог шукаються в тому ж тесті.
Фенікс

@steeldriver та Дженні: ви можете хотіти, щоб вона неоднозначність, а не просто вибирала одну. Змусити користувача вказати свій каталог, а не вибирати неправильний. (наприклад, відредагуйте сценарій, щоб встановити ім’я dir замість запуску коду автоматичного виявлення.)
Пітер Кордес,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.