Estaba haciendo una búsqueda muy simple:
grep -R Milledgeville ~/Documents
Y después de un tiempo apareció este error:
grep: memory exhausted
¿Cómo puedo evitar esto?
Tengo 10GB de RAM en mi sistema y pocas aplicaciones corriendo, así que estoy realmente sorprendido de que un simple grep se quede sin memoria. ~/Documents
tiene aproximadamente 100 GB y contiene todo tipo de archivos.
grep -RI
puede que no tenga este problema, pero quiero para buscar en archivos binarios también.
Respuesta
Dos problemas potenciales:
-
grep -R
(excepto para el GNUgrep
modificado que se encuentra en OS / X 10.8 y superior) sigue enlaces simbólicos, por lo que incluso si solo hay 100 GB de archivos en~/Documents
, es posible que todavía haya un enlace simbólico a/
, por ejemplo, y terminarás escaneando todo el sistema de archivos, incluido archivos como/dev/zero
. Usegrep -r
con la nuevagrep
de GNU, o use la sintaxis estándar:find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
(sin embargo, tenga en cuenta que el estado de salida no reflejará el hecho de que el patrón coincida o no).
-
grep
encuentra las líneas que coinciden con el patrón. Para eso, tiene que cargar una línea a la vez en la memoria. GNUgrep
a diferencia de muchos otrosgrep
Las implementaciones no tienen un límite en el tamaño de las líneas que lee y admiten la búsqueda en archivos binarios. Por lo tanto, si tiene un archivo con una línea muy grande (es decir, con dos caracteres de nueva línea muy alejados), más grande que la memoria disponible, fallará.Eso suele suceder con un archivo disperso. Puede reproducirlo con:
truncate -s200G some-file grep foo some-file
Es difícil solucionarlo. Podría hacerlo como (aún con GNU
grep
):find ~/Documents -type f -exec sh -c "for i do tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0" done" Milledgeville {} +
Eso convierte secuencias de caracteres NUL en un carácter de nueva línea antes de alimentar la entrada a
grep
. Eso cubriría los casos en los que el problema se debe a archivos dispersos.Puede optimizarlo haciéndolo solo para archivos grandes:
find ~/Documents -type f \( -size -100M -exec \ grep -He Milledgeville {} + -o -exec sh -c "for i do tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0" done" Milledgeville {} + \)
Si los archivos no son escasos y tiene una versión de GNU
grep
anterior a2.6
, puede usar la opción--mmap
. Las líneas se mmapearán en la memoria en lugar de copiar allí, lo que significa que el sistema siempre puede recuperar la memoria y paginando las páginas del archivo. Esa opción fue eliminada en GNUgrep
2.6
Comentarios
Respuesta
Normalmente lo hago
find ~/Documents | xargs grep -ne "expression"
Probé varios métodos y descubrí que este era el más rápido. Tenga en cuenta que esto no maneja muy bien archivos con espacios en el nombre del archivo. Si sabe que este es el caso y tiene una versión GNU de grep, puede usar:
find ~/Documents -print0 | xargs -0 grep -ne "expression"
De lo contrario, puede usar:
find ~/Documents -exec grep -ne "expression" "{}" \;
Lo cual exec
un grep para cada archivo.
Comentarios
- Esto romperá los archivos con espacios.
- Hmm, eso es cierto.
- Puede solucionar eso con
find -print0 | xargs -0 grep -ne 'expression'
- @ChrisDown en lugar de una solución no portátil que una solución portátil rota.
- @ChrisDown Most los principales unices han adoptado
find -print0
yxargs -0
ahora: los tres BSD, MINIX 3, Solaris 11,…
Respuesta
Puedo pensar en algunas formas de solucionar esto:
-
En su lugar de grepping todos los archivos a la vez, haz un archivo a la vez.Ejemplo:
find /Documents -type f -exec grep -H Milledgeville "{}" \;
-
Si solo necesita saber qué archivos contienen las palabras, haga
grep -l
en su lugar. Dado que grep dejará de buscar después del primer resultado, no tendrá que seguir leyendo archivos grandes -
Si también desea el texto real, puede encadenar dos greps separados a lo largo:
for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
Comentarios
- El último ejemplo no es una sintaxis válida; ‘ d necesita realizar una sustitución de comando (y no debe ‘ hacer eso, ya que
grep
salidas usando un delimitador que es legal en los nombres de archivo). También debe citar$file
. - El último ejemplo sufre con el problema de los nombres de archivo que tienen una nueva línea o espacios en blanco, (hará que
for
procese el archivo como dos argumentos) - @DravSloan Tu edición una mejora, todavía se rompe en los nombres de archivos legales.
- Sí, lo dejé porque era parte de su respuesta, solo traté de mejorarlo para que se ejecutara (para los casos en los que s sin espacios / líneas nuevas, etc. en los archivos).
- Correcciones de su – > ella, mis disculpas Jenny: /
Respuesta
Estoy usando un disco de 6TB para buscar datos perdidos y se agotó la memoria -error. Esto también debería funcionar para otros archivos.
La solución que se nos ocurrió fue leer el disco en fragmentos usando dd y grepping los fragmentos. Este es el código (big-grep.sh):
#problem: grep gives "memory exhausted" error on 6TB disks #solution: read it on parts if [ -z $2 ] || ! [ -e $1 ]; then echo "$0 file string|less -S # greps in chunks"; exit; fi FILE="$1" MATCH="$2" SIZE=`ls -l $1|cut -d\ -f5` CHUNKSIZE=$(( 1024 * 1024 * 1 )) CHUNKS=100 # greps in (100 + 1) x 1MB = 101MB chunks COUNT=$(( $SIZE / $CHUNKSIZE * CHUNKS )) for I in `seq 0 $COUNT`; do dd bs=$CHUNKSIZE skip=$(($I*$CHUNKS)) count=$(( $CHUNKS+1)) if=$FILE status=none|grep -UF -a --context 6 "$MATCH" done
Comentarios
- A menos que lea fragmentos superpuestos , es posible que se pierdan coincidencias en los límites del fragmento. La superposición debe ser al menos tan grande como la cadena que espera que coincida.
- Actualizado para buscar 1 MB extra en cada fragmento de 100 MB … truco barato
grep
puede descartar los búferes que ha procesado hasta ahora. Puedegrep
la salida deyes
indefinidamente sin utilizar más de unos pocos kilobytes de memoria. El problema es el tamaño de las líneas.--null-data
también puede ser útil aquí. Obliga al uso de NUL en lugar de newline como terminador de línea de entrada.dd if=/dev/sda | fold -b $((4096*1024*1024)) | grep -a "some string"
para limitar la cantidad de memoria requerida a 4GB