Я пытаюсь использовать 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 grep
here не будет работать для имен, содержащих символы новой строки, хотя они встречаются крайне редко и обычно представляют собой ошибку), часто гораздо проще сложить несколько экземпляров, grep
которые последовательно фильтруют результаты, используя более простые совпадения и обратные совпадения.-v
Это требует большей осторожности в отношении подстрок, чтобы убедиться, что вы действительно находите имя каталога, но в целом обеспечивает гораздо более простой для понимания синтаксис и может сделать все, что вам нужно!
find ./ | grep "/good/" | grep -v "/bad/" | grep '\.txt$'