Estoy buscando una manera de enumerar todos los archivos en un directorio que contiene el conjunto completo de palabras clave que estoy buscando, en cualquier parte del archivo.
Por lo tanto, las palabras clave no necesitan aparecer en la misma línea.
Una forma de hacer esto sería:
grep -l one $(grep -l two $(grep -l three *))
Tres palabras clave es solo un ejemplo, podría ser dos o cuatro, y así sucesivamente.
Una segunda forma en que puedo pensar es:
grep -l one * | xargs grep -l two | xargs grep -l three
Un tercer método, que apareció en otra pregunta , sería:
find . -type f \
-exec grep -q one {} \; -a \
-exec grep -q two {} \; -a \
-exec grep -q three {} \; -a -print
Pero definitivamente esa no es la dirección a la que voy aquí. Quiero algo que requiere menos escribir, y, posiblemente, sólo una llamada a grep
, awk
, perl
o similar.
Por ejemplo, me gusta cómo le awk
permite unir líneas que contienen todas las palabras clave , como:
awk '/one/ && /two/ && /three/' *
O imprima solo los nombres de archivo:
awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *
Pero quiero encontrar archivos donde las palabras clave pueden estar en cualquier parte del archivo, no necesariamente en la misma línea.
Las soluciones preferidas serían compatibles con gzip, por ejemplo, grep
tiene la zgrep
variante que funciona en archivos comprimidos. Por eso menciono esto, es que algunas soluciones pueden no funcionar bien dada esta restricción. Por ejemplo, en el awk
ejemplo de imprimir archivos coincidentes, no puede simplemente hacer:
zcat * | awk '/pattern/ {print FILENAME; nextfile}'
Necesita cambiar significativamente el comando, a algo como:
for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done
Entonces, debido a la restricción, debe llamar awk
muchas veces, aunque solo puede hacerlo una vez con archivos sin comprimir. Y ciertamente, sería mejor hacer zawk '/pattern/ {print FILENAME; nextfile}' *
y obtener el mismo efecto, por lo que preferiría soluciones que lo permitan.
gzip
amigables, solozcat
los archivos primero.grep
soluciones son fácilmente adaptables simplemente anteponiendogrep
llamadas con unz
, no es necesario que yo también maneje los nombres de los archivos.grep
. AFAIK, sologrep
ycat
tiene "variantes z" estándar. No creo que pueda obtener nada más simple que usar unafor f in *; do zcat -f $f ...
solución. Cualquier otra cosa tendría que ser un programa completo que verifique los formatos de archivo antes de abrir o use una biblioteca para hacer lo mismo.Respuestas:
Si desea manejar automáticamente los archivos comprimidos, ejecute esto en un bucle con
zcat
(lento e ineficiente porque se bifurcaráawk
muchas veces en un bucle, una vez para cada nombre de archivo) o vuelva a escribir el mismo algoritmoperl
y use elIO::Uncompress::AnyUncompress
módulo de biblioteca que puede descomprima varios tipos diferentes de archivos comprimidos (gzip, zip, bzip2, lzop). o en python, que también tiene módulos para manejar archivos comprimidos.Aquí hay una
perl
versión que utilizaIO::Uncompress::AnyUncompress
para permitir cualquier número de patrones y cualquier número de nombres de archivos (que contengan texto sin formato o texto comprimido).Todos los argumentos anteriores
--
se tratan como patrones de búsqueda. Todos los argumentos posteriores--
se tratan como nombres de archivo. Manejo de opciones primitivo pero efectivo para este trabajo. Mejor manejo opción (por ejemplo, para soportar una-i
opción para las búsquedas de mayúsculas y minúsculas) podría lograrse con elGetopt::Std
oGetopt::Long
los módulos.Ejecútelo así:
(No enumeraré archivos
{1..6}.txt.gz
y{1..6}.txt
aquí ... solo contienen algunas o todas las palabras "uno" "dos" "tres" "cuatro" "cinco" y "seis" para probar. Los archivos enumerados en el resultado anterior SÍ contiene los tres patrones de búsqueda. Pruébelo usted mismo con sus propios datos)Un hash
%patterns
contiene el conjunto completo de patrones que los archivos deben contener, al menos uno de cada miembro$_pstring
es una cadena que contiene las claves ordenadas de ese hash. La cadena$pattern
contiene una expresión regular precompilada también construida a partir del%patterns
hash.$pattern
se compara con cada línea de cada archivo de entrada (usando el/o
modificador para compilar$pattern
solo una vez, ya que sabemos que nunca cambiará durante la ejecución), ymap()
se usa para construir un hash (% s) que contiene las coincidencias para cada archivo.Siempre que se hayan visto todos los patrones en el archivo actual (al comparar si
$m_string
(las claves ordenadas en%s
) son iguales a$p_string
), imprima el nombre del archivo y pase al siguiente archivo.Esta no es una solución particularmente rápida, pero no es irrazonablemente lenta. La primera versión tardó 4m58s en buscar tres palabras en 74MB de archivos de registro comprimidos (un total de 937MB sin comprimir). Esta versión actual dura 1m13s. Probablemente hay más optimizaciones que podrían hacerse.
Una optimización obvia es usar esto junto con
xargs
'-P
aka'--max-procs
para ejecutar múltiples búsquedas en subconjuntos de archivos en paralelo. Para hacer eso, debe contar la cantidad de archivos y dividir por la cantidad de núcleos / cpus / hilos que tiene su sistema (y redondear agregando 1). por ejemplo, se buscaron 269 archivos en mi conjunto de muestras, y mi sistema tiene 6 núcleos (un AMD 1090T), por lo que:Con esa optimización, tomó solo 23 segundos encontrar los 18 archivos coincidentes. Por supuesto, lo mismo podría hacerse con cualquiera de las otras soluciones. NOTA: El orden de los nombres de archivo enumerados en la salida será diferente, por lo que puede ser necesario ordenarlos después si eso es importante.
Como señaló @arekolek, múltiples
zgrep
s confind -exec
oxargs
pueden hacerlo significativamente más rápido, pero este script tiene la ventaja de admitir cualquier número de patrones para buscar, y es capaz de manejar varios tipos diferentes de compresión.Si el script se limita a examinar solo las primeras 100 líneas de cada archivo, se ejecuta a través de todas ellas (en mi muestra de 74MB de 269 archivos) en 0.6 segundos. Si esto es útil en algunos casos, podría convertirse en una opción de línea de comando (por ejemplo
-l 100
), pero tiene el riesgo de no encontrar todos los archivos coincidentes.Por cierto, de acuerdo con la página del manual para
IO::Uncompress::AnyUncompress
, los formatos de compresión admitidos son:Una última (espero) optimización. Al usar el
PerlIO::gzip
módulo (empaquetado en Debian comolibperlio-gzip-perl
) en lugar de hacerloIO::Uncompress::AnyUncompress
, obtuve el tiempo de espera de aproximadamente 3,1 segundos para procesar mis 74 MB de archivos de registro. También hubo algunas pequeñas mejoras al usar un hash simple en lugar deSet::Scalar
(que también ahorró unos segundos con laIO::Uncompress::AnyUncompress
versión).PerlIO::gzip
fue recomendado como el gunzip perl más rápido en /programming//a/1539271/137158 (encontrado con una búsqueda en google paraperl fast gzip decompress
)Usar
xargs -P
con esto no lo mejoró en absoluto. De hecho, incluso pareció ralentizarlo entre 0.1 y 0.7 segundos. (Intenté cuatro ejecuciones y mi sistema hace otras cosas en segundo plano que alterarán el tiempo)El precio es que esta versión del script solo puede manejar archivos comprimidos y descomprimidos. Velocidad frente a flexibilidad: 3.1 segundos para esta versión frente a 23 segundos para la
IO::Uncompress::AnyUncompress
versión conxargs -P
envoltura (o 1m13s sinxargs -P
).fuente
for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; done
funciona bien, pero de hecho, toma 3 veces más que migrep
solución, y en realidad es más complicado.apt-get install libset-scalar-perl
que usar el guión. Pero no parece terminar en un tiempo razonable.Establezca el separador de registros para
.
queawk
trate el archivo completo como una línea:De manera similar con
perl
:fuente
for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; done
no produce nadazcat -f "$f"
si algunos de los archivos no están comprimidos.awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txt
aún no devuelve resultados, mientras quegrep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))
devuelve los resultados esperados.Para archivos comprimidos, puede recorrer cada archivo y descomprimir primero. Luego, con una versión ligeramente modificada de las otras respuestas, puede hacer:
El script de Perl se cerrará con
0
estado (éxito) si se encuentran las tres cadenas. La}{
es la abreviatura de PerlEND{}
. Todo lo que sigue se ejecutará después de que se haya procesado toda la entrada. Por lo tanto, el script saldrá con un estado de salida distinto de 0 si no se encuentran todas las cadenas. Por lo tanto,&& printf '%s\n' "$f"
imprimirá el nombre del archivo solo si se encontraron los tres.O, para evitar cargar el archivo en la memoria:
Finalmente, si realmente quieres hacer todo en un script, puedes hacer:
Guarde el script anterior como
foo.pl
en algún lugar de su$PATH
, hágalo ejecutable y ejecútelo así:fuente
De todas las soluciones propuestas hasta ahora, mi solución original usando grep es la más rápida, terminando en 25 segundos. Su inconveniente es que es tedioso agregar y eliminar palabras clave. Así que se me ocurrió una secuencia de comandos (doblada
multi
) que simula el comportamiento, pero permite cambiar la sintaxis:Ahora, escribir
multi grep one two three -- *
es equivalente a mi propuesta original y se ejecuta al mismo tiempo. También puedo usarlo fácilmente en archivos comprimidos usandozgrep
como primer argumento en su lugar.Otras soluciones
También experimenté con un script de Python usando dos estrategias: buscar todas las palabras clave línea por línea y buscar en todo el archivo palabra clave por palabra clave. La segunda estrategia fue más rápida en mi caso. Pero fue más lento que solo usar
grep
, terminando en 33 segundos. La coincidencia de palabras clave línea por línea finalizó en 60 segundos.El guión dado por terdon terminó en 54 segundos. En realidad, me llevó 39 segundos de tiempo en la pared, porque mi procesador es de doble núcleo. Lo cual es interesante, porque mi secuencia de comandos de Python tomó 49 segundos de tiempo de pared (y
grep
fue de 29 segundos).El script de cas no pudo finalizar en un tiempo razonable, incluso en un número menor de archivos procesados con
grep
menos de 4 segundos, por lo que tuve que matarlo.Pero su
awk
propuesta original , aunque es más lenta de logrep
que es, tiene una ventaja potencial. En algunos casos, al menos en mi experiencia, es posible esperar que todas las palabras clave aparezcan en algún lugar del encabezado del archivo si están en el archivo. Esto le da a esta solución un impulso dramático en el rendimiento:Termina en un cuarto de segundo, en lugar de 25 segundos.
Por supuesto, es posible que no tengamos la ventaja de buscar palabras clave que se sabe que ocurren cerca del comienzo de los archivos. En tal caso, la solución sin
NR>100 {exit}
toma 63 segundos (50 segundos de tiempo de pared).Archivos sin comprimir
No hay una diferencia significativa en el tiempo de ejecución entre mi
grep
solución y laawk
propuesta de cas , ambas tardan una fracción de segundo en ejecutarse.Tenga en cuenta que la inicialización de la variable
FNR == 1 { f1=f2=f3=0; }
es obligatoria en tal caso para restablecer los contadores para cada archivo procesado posterior. Como tal, esta solución requiere editar el comando en tres lugares si desea cambiar una palabra clave o agregar otras nuevas. Por otro lado, congrep
solo puede agregar| xargs grep -l four
o editar la palabra clave que desee.Una desventaja de la
grep
solución que utiliza la sustitución de comandos es que se bloqueará si en algún lugar de la cadena, antes del último paso, no hay archivos coincidentes. Esto no afecta laxargs
variante porque la tubería se abortará una vez quegrep
devuelva un estado distinto de cero. He actualizado mi script para usarlo,xargs
así que no tengo que manejar esto yo mismo, simplificando el script.fuente
not all(p in text for p in patterns)
not
) y terminó en 32 segundos, por lo que no es una gran mejora, pero ciertamente es más legible.PerlIO::gzip
lugar deIO::Uncompress::AnyUncompress
. ahora solo toma 3.1 segundos en lugar de 1m13s para procesar mis 74MB de archivos de registro.eval $(lesspipe)
(por ejemplo, en su.profile
, etc.), puede usar enless
lugar dezcat -f
y sufor
envoltura de bucleawk
podrá procesar cualquier tipo de archivo queless
pueda (gzip, bzip2, xz y más) ... less puede detectar si stdout es una tubería y solo generará una secuencia en stdout si lo es.Otra opción: alimentar palabras de una en una para
xargs
para que se ejecutegrep
contra el archivo.xargs
se puede hacer que salga tan pronto como una invocación degrep
devolución devuelva el error255
(consulte laxargs
documentación). Por supuesto, el desove de las conchas y las bifurcaciones involucradas en esta solución probablemente disminuirá significativamentey para enrollarlo
fuente
_
yfile
? ¿Esta búsqueda en múltiples archivos se pasará como argumento y devolverá archivos que contienen todas las palabras clave?_
, se pasa como$0
al shell generado - esto se mostraría como el nombre del comando en la salida deps
- Me