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, perlo similar.
Por ejemplo, me gusta cómo le awkpermite 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, greptiene la zgrepvariante 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 awkejemplo 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 awkmuchas 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.

gzipamigables, solozcatlos archivos primero.grepsoluciones son fácilmente adaptables simplemente anteponiendogrepllamadas con unz, no es necesario que yo también maneje los nombres de los archivos.grep. AFAIK, sologrepycattiene "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áawkmuchas veces en un bucle, una vez para cada nombre de archivo) o vuelva a escribir el mismo algoritmoperly use elIO::Uncompress::AnyUncompressmó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
perlversión que utilizaIO::Uncompress::AnyUncompresspara 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-iopción para las búsquedas de mayúsculas y minúsculas) podría lograrse con elGetopt::StdoGetopt::Longlos módulos.Ejecútelo así:
(No enumeraré archivos
{1..6}.txt.gzy{1..6}.txtaquí ... 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
%patternscontiene el conjunto completo de patrones que los archivos deben contener, al menos uno de cada miembro$_pstringes una cadena que contiene las claves ordenadas de ese hash. La cadena$patterncontiene una expresión regular precompilada también construida a partir del%patternshash.$patternse compara con cada línea de cada archivo de entrada (usando el/omodificador para compilar$patternsolo 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'-Paka'--max-procspara 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
zgreps confind -execoxargspueden 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::gzipmó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::AnyUncompressversión).PerlIO::gzipfue 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 -Pcon 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::AnyUncompressversión conxargs -Penvoltura (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 }'; donefunciona bien, pero de hecho, toma 3 veces más que migrepsolución, y en realidad es más complicado.apt-get install libset-scalar-perlque usar el guión. Pero no parece terminar en un tiempo razonable.Establezca el separador de registros para
.queawktrate 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 }'; doneno produce nadazcat -f "$f"si algunos de los archivos no están comprimidos.awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txtaú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
0estado (é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.plen 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 usandozgrepcomo 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
grepfue de 29 segundos).El script de cas no pudo finalizar en un tiempo razonable, incluso en un número menor de archivos procesados con
grepmenos de 4 segundos, por lo que tuve que matarlo.Pero su
awkpropuesta original , aunque es más lenta de logrepque 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
grepsolución y laawkpropuesta 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, congrepsolo puede agregar| xargs grep -l fouro editar la palabra clave que desee.Una desventaja de la
grepsolució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 laxargsvariante porque la tubería se abortará una vez quegrepdevuelva un estado distinto de cero. He actualizado mi script para usarlo,xargsasí 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::gziplugar 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 enlesslugar dezcat -fy suforenvoltura de bucleawkpodrá procesar cualquier tipo de archivo quelesspueda (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
xargspara que se ejecutegrepcontra el archivo.xargsse puede hacer que salga tan pronto como una invocación degrepdevolución devuelva el error255(consulte laxargsdocumentació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$0al shell generado - esto se mostraría como el nombre del comando en la salida deps- Me