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 -path
para eso, pero también puede indicar find
que 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=C
por lo que el comodín find
de ' *
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 bad
los 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 C
sea similar a find
lo 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 grep
o compatible):
regex='^(?!.*/bad/).*/good/.*\.txt\Z'
find . -type f -print0 |
LC_ALL=C grep -zPe "$regex" |
tr '\0' '\n'
(aunque find
todavía descubre todos los archivos en todos bad
los directorios).
Reemplácelo tr '\0' '\n'
con xargs -r0 cmd
si necesita hacer algo con esos archivos (aparte de imprimirlos uno por línea).
¹ En cualquier caso, no conozco ninguna find
implementació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 -path
predicado 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 find
el poderoso filtrado de (por ejemplo, naive grep
aquí 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 grep
que 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$'