Buscar: use expresiones regulares para obtener todos los archivos con un nombre de directorio específico en la ruta, pero sin otro nombre de directorio específico en la ruta

Buscar: use expresiones regulares para obtener todos los archivos con un nombre de directorio específico en la ruta, pero sin otro nombre de directorio específico en la ruta

Estoy intentando usar buscar para devolver todos los nombres de archivos que tienen un directorio específico en su ruta, pero no tienen otro directorio específico en ninguna parte de la ruta del archivo. Algo como:

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

Sé que también podría hacer esto conectando un comando de búsqueda a otro, pero me gustaría saber cómo hacerlo con una sola expresión regular.

Por ejemplo, quiero todos los archivos que tengan el directorio "bueno" en su ruta, pero que no tengan el directorio "malo" en ninguna parte de su ruta, sin importar la combinación. Algunos ejemplos:

/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

Tenga en cuenta que algunos nombres de archivos pueden contener "buenos" o "malos", pero solo quiero tener en cuenta los nombres de los directorios.

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

Mi investigación sugiere que debería utilizar una mirada hacia adelante negativa y una mirada hacia atrás negativa. Sin embargo, nada de lo que he probado ha funcionado hasta ahora. Un poco de ayuda sería apreciada. Gracias.

Respuesta1

Como dijo Inian, no es necesario -regex(lo cual no es estándar y la sintaxis varía mucho entre las implementaciones que sí lo admiten -regex¹).

Puede usarlo -pathpara eso, pero también puede indicar findque no ingrese a directorios llamados bad, lo que sería más eficiente que descubrir todos los archivos que contienen para luego filtrarlos con -path:

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

( LC_ALL=Cpor lo que el comodín findde ' *no se ahoga con los nombres de archivos con una secuencia de bytes que no forman caracteres válidos en la configuración regional).

O para más de un nombre de carpeta:

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

Con zsh, también puedes hacer:

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

O para las listas en matrices:

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

Anodescender a directorios llamados bad, o (menos eficiente como con -path '*/good/*' ! -path '*/bad/*'):

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

En zsh -o extendedglob, ~es elexcepto(y-no) operador global while ^es el operador de negación y #es 0 o más de lo anterior como regexp *. ${(~j[|])array}une los elementos de la matriz con |, y |se trata como un operador global en lugar de un literal |con ~.

En zsh, podrá utilizar la coincidencia PCRE después de set -o rematchpcre:

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

Pero es probable que esa evaluación del código shell para cada archivo (incluidos los de badlos directorios) la haga mucho más lenta que otras soluciones.

También tenga en cuenta que PCRE (a diferencia de zsh globs) se ahogaría con secuencias de bytes que no forman caracteres válidos en la configuración regional y no admite conjuntos de caracteres multibyte distintos de UTF-8. Arreglar la configuración regional para que Csea similar a findlo anterior solucionaría ambos problemas para este patrón en particular.

Si prefiere [[ =~ ]]solo hacer coincidencias de expresiones regulares extendidas como en bash, también puede simplemente cargar el módulo pcre ( zmodload zsh/pcre) y usarlo [[ -pcre-match ]]en lugar de [[ =~ ]]para hacer coincidencias PCRE.

O podrías hacer el filtrado con grep -zP(asumiendo GNU grepo compatible):

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

(aunque findtodavía descubre todos los archivos en todos badlos directorios).

Reemplácelo tr '\0' '\n'con xargs -r0 cmdsi necesita hacer algo con esos archivos (aparte de imprimirlos uno por línea).


¹ En cualquier caso, no conozco ninguna findimplementación que admita expresiones regulares tipo perl o vim que necesitarías para los operadores de búsqueda.

Respuesta2

No necesita una expresión regular para esto, puede usar el -pathpredicado para excluir directorios con un nombre específico en cualquier nivel.

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

Respuesta3

Si bien es probable que sea menos eficiente (¡aunque no estoy seguro!) y menos "correcto" que findel poderoso filtrado de (por ejemplo, naive grepaquí no funcionará para nombres que contengan caracteres de nueva línea, aunque estos son extremadamente raros y normalmente representan un error) , a menudo es mucho más fácil apilar algunas instancias grepque filtran sucesivamente los resultados utilizando coincidencias más simples y coincidencias inversas.-v

Esto requiere más precaución con las subcadenas para garantizar que realmente esté buscando un nombre de directorio, pero generalmente brindará una sintaxis mucho más fácil de entender y puede hacer todo lo que necesita.

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

información relacionada