grep: mémoire épuisée

42

Je faisais une recherche très simple:

grep -R Milledgeville ~/Documents

Et après un certain temps, cette erreur est apparue:

grep: memory exhausted

Comment puis-je éviter ça?

J'ai 10 Go de RAM sur mon système et peu d'applications en cours d'exécution, donc je suis vraiment surpris qu'un simple grep manque de mémoire. ~/Documentsest d'environ 100 Go et contient toutes sortes de fichiers.

grep -RI peut-être pas ce problème, mais je veux aussi chercher dans des fichiers binaires.

Nicolas Raoul
la source

Réponses:

46

Deux problèmes potentiels:

  • grep -R(à l'exception de la version modifiée de GNU grepdisponible sur OS / X 10.8 et versions ultérieures) suit les liens symboliques. Ainsi, même s'il ne contient que 100 Go de fichiers ~/Documents, il peut toujours y avoir un lien symbolique vers /par exemple et vous finirez par analyser l'ensemble du système de fichiers, fichiers compris. comme /dev/zero. Utilisez cette version grep -ravec GNU grepou utilisez la syntaxe standard suivante:

    find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
    

    (notez cependant que l'état de sortie ne reflétera pas le fait que le modèle correspond ou non).

  • greptrouve les lignes qui correspondent au motif. Pour cela, il doit charger une ligne à la fois en mémoire. GNU grep, contrairement à de nombreuses autres grepimplémentations, ne limite pas la taille des lignes lues et prend en charge la recherche dans les fichiers binaires. Donc, si vous avez un fichier avec une très grosse ligne (c'est-à-dire avec deux caractères de nouvelle ligne très éloignés), plus grand que la mémoire disponible, cela échouera.

    Cela se produirait généralement avec un fichier fragmenté. Vous pouvez le reproduire avec:

    truncate -s200G some-file
    grep foo some-file
    

    Celui-là est difficile à contourner. Vous pouvez le faire comme (toujours avec GNU grep):

    find ~/Documents -type f -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} +
    

    Cela convertit les séquences de caractères NUL en un caractère de nouvelle ligne avant d’alimenter l’entrée grep. Cela couvrirait les cas où le problème est dû à la rareté des fichiers.

    Vous pouvez l'optimiser en ne le faisant que pour les gros fichiers:

    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 les fichiers ne sont pas clairsemés et que vous avez une version grepantérieure de GNU 2.6, vous pouvez utiliser cette --mmapoption. Les lignes seront mappées en mémoire et non copiées, ce qui signifie que le système peut toujours récupérer la mémoire en paginant les pages du fichier. Cette option a été supprimée dans GNU grep2.6.

Stéphane Chazelas
la source
En fait, GNU grep ne se soucie pas de lire sur une ligne, il lit une grande partie du fichier dans un seul tampon. "En outre, GNU grep évite de couper les entrées en lignes." source: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer
4
@GodricSeer, il peut toujours lire une grande partie du fichier dans un seul tampon, mais s'il ne trouve pas la chaîne dedans et n'a pas trouvé de caractère de nouvelle ligne non plus, mon pari est qu'il garde ce tampon unique en mémoire et lit le tampon suivant, car il devra l'afficher si une correspondance est trouvée. Donc, le problème est toujours le même. En pratique, un grep sur un fichier fragmenté de 200 Go échoue avec le MOO.
Stéphane Chazelas
1
@GodricSeer, ben non. Si les lignes sont toutes petites, vous greppouvez supprimer les tampons traités jusqu’à présent. Vous pouvez grepsortir yesindéfiniment sans utiliser plus de quelques kilo-octets de mémoire. Le problème est la taille des lignes.
Stéphane Chazelas
3
L' --null-dataoption GNU grep peut également être utile ici. Il force l'utilisation de NUL au lieu de newline comme terminateur de ligne d'entrée.
iruvar
1
@ 1_CR, bon point, même si cela définit également le terminateur de ligne de sortie sur NUL.
Stéphane Chazelas
5

Je fais habituellement

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

J'ai essayé un tas de méthodes et j'ai trouvé que c'était le plus rapide. Notez que cela ne gère pas très bien les fichiers avec des espaces, le nom de fichier. Si vous savez que c'est le cas et que vous avez une version GNU de grep, vous pouvez utiliser:

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

Sinon, vous pouvez utiliser:

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

Quel sera execun grep pour chaque fichier.

Kotte
la source
Cela cassera sur les fichiers avec des espaces.
Chris Down
Hmm, c'est vrai.
Kotte
Vous pouvez contourner cela avecfind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan
@ChrisDown plutôt une solution non protable qu'une solution portable cassée.
Reto
@ChrisDown La plupart des grands bureaux ont adopté find -print0et xargs -0à ce jour: les trois BSD, MINIX 3, Solaris 11,…
Gilles 'SO - arrête d'être méchant'
4

Je peux penser à quelques façons de contourner cela:

  • Au lieu de mettre tous les fichiers à la fois, créez un fichier à la fois. Exemple:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Si vous avez seulement besoin de savoir quels fichiers contiennent les mots, faites-le grep -lplutôt. Puisque grep arrêtera de chercher après le premier coup, il ne sera pas obligé de lire de gros fichiers.

  • Si vous souhaitez également obtenir le texte réel, vous pouvez également y insérer deux groupes distincts:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    
Jenny D
la source
Le dernier exemple n'est pas une syntaxe valide - vous devez effectuer une substitution de commande (et vous ne devriez pas le faire, car les grepsorties utilisent un délimiteur qui est légal dans les noms de fichiers). Vous devez également citer $file.
Chris Down
Ce dernier exemple souffre du problème des noms de fichiers comportant des espaces et des nouvelles lignes (le fortraitement du fichier sera alors traité comme deux arguments)
Drav Sloan
@DravSloan Votre édition, bien que l'amélioration, rompt toujours sur les noms de fichiers légaux.
Chris Down
1
Oui, je l'ai laissée parce que cela faisait partie de sa réponse. J'ai simplement essayé de l'améliorer pour qu'elle fonctionne (pour les cas où il n'y a pas d'espaces / de nouvelles lignes, etc. dans les fichiers).
Drav Sloan
Correction de son -> elle, mes excuses Jenny: /
Drav Sloan
1

Je cherche un disque de 6 To pour rechercher les données perdues et la mémoire est épuisée - erreur. Cela devrait fonctionner pour d'autres fichiers aussi.

La solution que nous avons proposée consistait à lire le disque en morceaux en utilisant dd et en les attrapant. C'est le code (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
la source
1
Si vous ne lisez pas des morceaux qui se chevauchent , vous risquez de manquer des correspondances sur les limites des morceaux. Le chevauchement doit être au moins aussi grand que la chaîne à laquelle vous vous attendez.
Kusalananda
Mis à jour pour rechercher 1MB supplémentaire dans chaque morceau de 100Mo ... bon marché bidouille
Dagelf