Localizar: Use regex para obter todos os arquivos com nome de diretório específico no caminho, mas sem outro nome de diretório específico no caminho

Localizar: Use regex para obter todos os arquivos com nome de diretório específico no caminho, mas sem outro nome de diretório específico no caminho

Estou tentando usar find para retornar todos os nomes de arquivos que possuem um diretório específico em seu caminho, mas não possuem outro diretório específico em nenhum lugar no caminho do arquivo. Algo como:

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

Eu sei que também posso fazer isso canalizando um comando find para outro, mas gostaria de saber como fazer isso com uma única expressão regular.

Por exemplo, quero todos os arquivos que tenham o diretório "bom" em seu caminho, mas não tenham o diretório "ruim" em nenhum lugar do caminho, independentemente da combinação. Alguns exemplos:

/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

Tenha em mente que alguns nomes de arquivos podem conter "bons" ou "ruins", mas quero considerar apenas nomes de diretórios.

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

Minha pesquisa sugere que eu deveria usar um Lookahead Negativo e um Lookbehind Negativo. No entanto, nada que tentei funcionou até agora. Alguma ajuda seria apreciada. Obrigado.

Responder1

Como disse Inian, você não precisa -regex(o que não é padrão e a sintaxe varia muito entre as implementações que suportam -regex¹).

Você pode usar -pathpara isso, mas também pode dizer findpara não entrar em diretórios chamados bad, o que seria mais eficiente do que descobrir todos os arquivos neles para depois filtrá-los com -path:

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

( LC_ALL=Centão findo *curinga de não engasga com nomes de arquivos com sequência de bytes que não formam caracteres válidos no código do idioma).

Ou para mais de um nome de pasta:

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

Com zshvocê também pode fazer:

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

Ou para as listas em arrays:

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

Paranãodesça em diretórios chamados badou (menos eficiente como com -path '*/good/*' ! -path '*/bad/*'):

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

Em zsh -o extendedglob, ~é oexceto(e não) operador globbing while ^é o operador de negação e #é 0-ou-mais-da-coisa-precedente como regexp *. ${(~j[|])array}une os elementos da matriz com |, |sendo tratado como um operador glob em vez de um literal |com ~.

Em zsh, você poderá usar a correspondência PCRE depois de set -o rematchpcre:

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

Mas essa avaliação do código shell para cada arquivo (incluindo aqueles em baddiretórios) provavelmente o tornará muito mais lento do que outras soluções.

Tenha também em atenção que o PCRE (ao contrário dos zsh globs) engasgaria com sequências de bytes que não formam caracteres válidos no local e não suporta conjuntos de caracteres multibyte diferentes de UTF-8. Corrigir a localidade como Cacima findabordaria ambos para esse padrão específico.

Se você preferir [[ =~ ]]apenas fazer a correspondência estendida de regexp como em bash, você também pode simplesmente carregar o módulo pcre ( zmodload zsh/pcre) e usar [[ -pcre-match ]]em vez de [[ =~ ]]para fazer a correspondência PCRE.

Ou você poderia fazer a filtragem com grep -zP(assumindo GNU grepou compatível):

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

(embora findainda descubra todos os arquivos em todos os baddiretórios).

Substitua tr '\0' '\n'por xargs -r0 cmdse precisar fazer alguma coisa com esses arquivos (além de imprimi-los um por linha).


¹ De qualquer forma, não conheço nenhuma findimplementação que suporte expressões regulares semelhantes a Perl ou Vim, necessárias para operadores de pesquisa.

Responder2

Você não precisa de um regex para isso, você pode usar o -pathpredicado para excluir diretórios com um nome específico em qualquer nível

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

Responder3

Embora seja provavelmente menos eficiente (embora eu não tenha certeza!) E menos "correto" do que finda filtragem poderosa de (por exemplo, ingênuo grepaqui não funcionará para nomes contendo caracteres de nova linha, embora estes sejam extremamente raros e normalmente representem um erro) , muitas vezes é muito mais fácil empilhar algumas instâncias grepque filtram sucessivamente os resultados usando correspondências mais simples e correspondências inversas-v

Isso exige mais cuidado com as substrings para garantir que você realmente encontre um nome de diretório, mas geralmente fornecerá uma sintaxe muito mais fácil de entender e poderá fazer tudo o que você precisa!

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

informação relacionada