grep: memoria agotada

42

Estaba haciendo una búsqueda muy simple:

grep -R Milledgeville ~/Documents

Y después de algún tiempo apareció este error:

grep: memory exhausted

¿Cómo puedo evitar esto?

Tengo 10 GB de RAM en mi sistema y pocas aplicaciones ejecutándose, así que estoy realmente sorprendido de que un simple grep se quede sin memoria. ~/Documentstiene aproximadamente 100 GB y contiene todo tipo de archivos.

grep -RI Es posible que no tenga este problema, pero también quiero buscar en archivos binarios.

Nicolas Raoul
fuente

Respuestas:

46

Dos posibles problemas:

  • grep -R(a excepción del GNU modificado que se grepencuentra en OS / X 10.8 y superior) sigue los enlaces simbólicos, por lo que incluso si solo hay 100 GB de archivos ~/Documents, podría haber un enlace simbólico, /por ejemplo, y terminará escaneando todo el sistema de archivos, incluidos los archivos como /dev/zero. Use grep -rcon GNU más nuevo grep, 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 coincide o no).

  • grepencuentra las líneas que coinciden con el patrón. Para eso, tiene que cargar una línea a la vez en la memoria. GNU grep, a diferencia de muchas otras grepimplementaciones, no tiene un límite en el tamaño de las líneas que lee y admite la búsqueda en archivos binarios. Entonces, si tiene un archivo con una línea muy grande (es decir, con dos caracteres de nueva línea muy separados), más grande que la memoria disponible, fallará.

    Eso normalmente sucedería con un archivo disperso. Puedes reproducirlo con:

    truncate -s200G some-file
    grep foo some-file
    

    Ese es difícil de evitar. Podrías hacerlo como (todavía 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 grep. Eso cubriría los casos en que el problema se deba a la escasez de archivos.

    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 grepanterior 2.6, puede usar la --mmapopción. Las líneas se mapearán en la memoria en lugar de copiarse allí, lo que significa que el sistema siempre puede recuperar la memoria paginando las páginas al archivo. Esa opción fue eliminada en GNU grep2.6

Stéphane Chazelas
fuente
En realidad, a GNU grep no le importa leer en 1 línea, lee una gran parte del archivo en un solo búfer. "Además, GNU grep EVITA ROMPER LA ENTRADA EN LÍNEAS". fuente: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer
44
@GodricSeer, aún puede leer una gran parte del archivo en un solo búfer, pero si no ha encontrado la cadena allí y tampoco ha encontrado un carácter de nueva línea, mi apuesta es que mantiene ese único búfer en la memoria y lee el siguiente búfer, ya que tendrá que mostrarlo si se encuentra una coincidencia. Entonces, el problema sigue siendo el mismo. En la práctica, un grep en un archivo disperso de 200 GB falla con OOM.
Stéphane Chazelas
1
@GodricSeer, bueno no. Si todas las líneas son pequeñas, greppuede descartar los búferes que ha procesado hasta ahora. Puede grepgenerar la salida de forma yesindefinida sin utilizar más de unos pocos kilobytes de memoria. El problema es el tamaño de las líneas.
Stéphane Chazelas
3
La --null-dataopción grep de GNU también puede ser útil aquí. Obliga al uso de NUL en lugar de nueva línea como un terminador de línea de entrada.
iruvar
1
@ 1_CR, buen punto, aunque eso también establece el terminador de línea de salida en NUL.
Stéphane Chazelas
5

Yo suelo hacer

find ~/Documents | xargs grep -ne 'expression'

Probé varios métodos y descubrí que este es el más rápido. Tenga en cuenta que esto no maneja muy bien los archivos con espacios del 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'

Si no, puedes usar:

 find ~/Documents -exec grep -ne 'expression' "{}" \;

Lo cual será execun grep para cada archivo.

Kotte
fuente
Esto se romperá en archivos con espacios.
Chris Down
Hmm, eso es verdad.
Kotte
Usted puede obtener en torno a que confind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan
@ChrisDown más bien una solución no protable que una solución portátil rota.
reto
@ChrisDown La mayoría de los principales unices han adoptado find -print0y xargs -0ahora: los tres BSD, MINIX 3, Solaris 11, ...
Gilles 'SO- deja de ser malvado'
4

Se me ocurren algunas maneras de evitar esto:

  • En lugar de agrupar todos los archivos a la vez, haga un archivo a la vez. Ejemplo:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Si solo necesita saber qué archivos contienen las palabras, hágalo grep -l. Dado que grep dejará de buscar después del primer golpe, no tendrá que seguir leyendo ningún archivo enorme

  • Si también desea el texto real, puede encadenar dos greps separados:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    
Jenny D
fuente
El último ejemplo no es una sintaxis válida: necesitaría realizar una sustitución de comando (y no debería hacerlo, ya que las grepsalidas usan un delimitador que es legal en los nombres de archivo). También necesita cotizar $file.
Chris Down
El último ejemplo sufre con el problema de los nombres de archivo que tienen nueva línea o espacios en blanco (causará forque el archivo se procese como dos argumentos)
Drav Sloan
@DravSloan Su edición, aunque es una mejora, aún se rompe en los nombres de archivos legales.
Chris Down
1
Sí, lo dejé porque era parte de su respuesta, solo intenté mejorarlo para que funcionara (para los casos en que no hay espacios / líneas nuevas, etc. en los archivos).
Drav Sloan
Correcciones de su -> ella, mis disculpas Jenny: /
Drav Sloan
1

Estoy usando un disco de 6TB para buscar datos perdidos, y la memoria se agotó. Esto también debería funcionar para otros archivos.

La solución que se nos ocurrió fue leer el disco en fragmentos utilizando dd y haciendo un grep de 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
PHZ.fi-Pharazon
fuente
1
A menos que lea fragmentos superpuestos , posiblemente se perderá partidos en los límites de los fragmentos. La superposición debe ser al menos tan grande como la cadena que espera que coincida.
Kusalananda
Actualizado para buscar 1 MB extra en cada fragmento de 100 MB ... truco barato
Dagelf