Найти: Используйте регулярное выражение, чтобы получить все файлы с определенным именем каталога в пути, но без другого определенного имени каталога в пути.

Найти: Используйте регулярное выражение, чтобы получить все файлы с определенным именем каталога в пути, но без другого определенного имени каталога в пути.

Я пытаюсь использовать find для возврата всех имен файлов, которые имеют определенный каталог в своем пути, но не имеют другого определенного каталога нигде в пути файла. Что-то вроде:

myRegex= <regex> 
targetDir= <source directory>
find $targetDir -regex $myRegex -print

Я знаю, что это можно сделать и путем объединения одной команды find в другую, но мне хотелось бы узнать, как это сделать с помощью одного регулярного выражения.

Например, мне нужны все файлы, в пути которых есть каталог "good", но нет каталога "bad" нигде в пути, независимо от комбинации. Несколько примеров:

/good/file_I_want.txt #Captured
/good/bad/file_I_dont_want.txt #Not captured

/dir1/good/file_I_want.txt #Captured
/dir2/good/bad/file_I_dont_want.txt #Not captured

/dir1/good/dir2/file_I_want.txt #Captured
/dir1/good/dir2/bad/file_I_want.txt #Not captured

/bad/dir1/good/file_I_dont_want.txt #Not captured

Имейте в виду, что некоторые имена файлов могут содержать слова «хороший» или «плохой», но я хочу учитывать только имена каталогов.

/good/bad.txt #Captured
/bad/good.txt #Not captured

Мои исследования показывают, что мне следует использовать отрицательный просмотр вперед и отрицательный просмотр назад. Однако, ничего из того, что я пробовал, пока не сработало. Буду признателен за помощь. Спасибо.

решение1

Как сказал Иниан, вам это не нужно -regex(что нестандартно, и синтаксис сильно различается в реализациях, которые поддерживают -regex¹).

Для этого можно использовать -path, но можно также запретить findвходить в каталоги с именем bad, что будет эффективнее, чем обнаруживать в них каждый файл для последующей фильтрации с помощью -path:

LC_ALL=C find . -name bad -prune -o -path '*/good/*.txt' -type f -print

( LC_ALL=Cчтобы подстановочный findзнак *не подавлял имена файлов последовательностью байтов, не образующих допустимые символы в локали).

Или для более чем одного имени папки:

LC_ALL=C find . '(' -name bad -o -name worse ')' -prune -o \
  '(' -path '*/good/*' -o -path '*/better/*' ')' -name '*.txt' -type f -print

С помощью zshвы также можете сделать:

set -o extendedglob # best in ~/.zshrc
print -rC1 -- (^bad/)#*.txt~^*/good/*(ND.)
print -rC1 -- (^(bad|worse)/)#*.txt~^*/(good|better)/*(ND.)

Или для списков в массивах:

good=(good better best)
bad=(bad worse worst)
print -rC1 -- (^(${(~j[|])bad})/)#*.txt~^*/(${(~j[|])good})/*(ND.)

Кнетспуститься в каталоги, называемые bad, или (менее эффективно, как с -path '*/good/*' ! -path '*/bad/*'):

print -rC1 -- **/*.txt~*/bad/*~^*/good/*(ND.)

В zsh -o extendedglob, ~являетсякромеОператор подстановки (и-не), в то время как ^является оператором отрицания и #представляет собой 0 или более предшествующих элементов, как и regexp *. ${(~j[|])array}объединяет элементы массива с |, при этом он |рассматривается как оператор подстановки, а не как литерал |с ~.

В zshвы сможете использовать сопоставление PCRE после set -o rematchpcre:

set -o rematchpcre
regex='^(?!.*/bad/).*/good/.*\.txt\Z'
print -rC1 -- **/*(ND.e['[[ $REPLY =~ $regex ]]'])

Однако такая оценка кода оболочки для каждого файла (включая файлы в badкаталогах) скорее всего сделает его намного медленнее, чем другие решения.

Также имейте в виду, что PCRE (в отличие от zsh globs) будет подавляться последовательностями байтов, которые не образуют допустимые символы в локали, и не поддерживает многобайтовые кодировки, отличные от UTF-8. Исправление локали на Cподобие findвыше устранит обе проблемы для этого конкретного шаблона.

Если вы предпочитаете [[ =~ ]]выполнять только расширенное сопоставление регулярных выражений, как в bash, вы также можете просто загрузить модуль pcre ( zmodload zsh/pcre) и использовать [[ -pcre-match ]]вместо [[ =~ ]]для сопоставления PCRE.

Или вы можете выполнить фильтрацию с помощью grep -zP(предполагая, что это GNU grepили совместимое):

regex='^(?!.*/bad/).*/good/.*\.txt\Z'
find . -type f -print0 |
  LC_ALL=C grep -zPe "$regex" |
  tr '\0' '\n'

(хотя findвсе равно обнаруживает все файлы во всех badкаталогах).

Замените tr '\0' '\n'на , xargs -r0 cmdесли вам нужно что-то сделать с этими файлами (кроме их печати по одному в строке).


¹ В любом случае, я не знаю ни одной findреализации, которая поддерживала бы регулярные выражения в стиле Perl или Vim, которые могут понадобиться для операторов поиска.

решение2

Для этого вам не нужно регулярное выражение, вы можете использовать предикат, -pathчтобы исключить каталоги с определенным именем на любом уровне.

find . -type f -path '*/good/*' '!' -path '*/bad/*'

решение3

Хотя это, вероятно, менее эффективно (хотя я не уверен!) и менее «правильно», чем findмощная фильтрация (например, naive grephere не будет работать для имен, содержащих символы новой строки, хотя они встречаются крайне редко и обычно представляют собой ошибку), часто гораздо проще сложить несколько экземпляров, grepкоторые последовательно фильтруют результаты, используя более простые совпадения и обратные совпадения.-v

Это требует большей осторожности в отношении подстрок, чтобы убедиться, что вы действительно находите имя каталога, но в целом обеспечивает гораздо более простой для понимания синтаксис и может сделать все, что вам нужно!

find ./ | grep "/good/" | grep -v "/bad/" | grep '\.txt$'

Связанный контент