Как передать регулярное выражение при поиске пути к каталогу в 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. Так не будет ли xxxconda * лучше?
WinEunuuchs2Unix
2
Расширение имени файла Bash использует выражения glob, а не регулярные выражения.
Питер Кордес

Ответы:

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])/ )

steeldriver
источник
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
@ Дженни, пожалуйста, ознакомьтесь с моей globstar
редакцией
1
@terdon Да, я действительно не хотел идти по кроличьей норе, что является «правильным», чтобы соответствовать - я просто взял регулярное выражение OP как есть, чтобы проиллюстрировать общий подход
Steeldriver
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для каждой вещи, которая удовлетворяет предыдущим условиям.
PerlDuck
источник
Когда есть матч. -bash: -regex: command not found found one of the directories
Дженни
Привет, PerlDuck: Спасибо. Хороший ответ тоже. Но я получаю сообщение об ошибке. printfНапример, когда я запускаю скрипт, он работает нормально, но не находит команду printf, когда нет совпадения, но я думаю, что это потому, что нечего печатать, может быть ?. -bash: -printf: command not found no match.
Дженни
3
@ Дженни Возможно, вы сделали опечатку при копировании, так как она отлично работает для меня. -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 вместо запуска кода автоопределения.)
Питер Кордес