grep: Speicher erschöpft

42

Ich habe eine sehr einfache Suche durchgeführt:

grep -R Milledgeville ~/Documents

Und nach einiger Zeit trat dieser Fehler auf:

grep: memory exhausted

Wie kann ich das vermeiden?

Ich habe 10 GB RAM auf meinem System und es werden nur wenige Anwendungen ausgeführt. Ich bin wirklich überrascht, dass ein einfaches grep keinen Speicher mehr hat. ~/Documentsist etwa 100 GB groß und enthält alle Arten von Dateien.

grep -RI Möglicherweise besteht dieses Problem nicht, aber ich möchte auch in Binärdateien suchen.

Nicolas Raoul
quelle

Antworten:

46

Zwei mögliche Probleme:

  • grep -R(mit Ausnahme des modifizierten GNU, grepdas unter OS / X 10.8 und höher zu finden ist) folgt Symlinks. Selbst wenn nur 100 GB Dateien enthalten sind ~/Documents, ist möglicherweise immer noch ein Symlink zu vorhanden, /und Sie werden das gesamte Dateisystem einschließlich der Dateien durchsuchen wie /dev/zero. Verwenden Sie grep -rmit neueren GNU grep, oder verwenden Sie die Standardsyntax:

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

    (Beachten Sie jedoch, dass der Beendigungsstatus nicht die Tatsache widerspiegelt, dass das Muster übereinstimmt oder nicht).

  • grepFindet die Linien, die dem Muster entsprechen. Dazu muss jeweils eine Zeile in den Speicher geladen werden. GNU hat grepim Gegensatz zu vielen anderen grepImplementierungen keine Begrenzung für die Größe der gelesenen Zeilen und unterstützt die Suche in Binärdateien. Wenn Sie also eine Datei mit einer sehr großen Zeile haben (dh mit zwei Zeilenumbrüchen, die sehr weit entfernt sind), die größer als der verfügbare Speicher ist, schlägt der Vorgang fehl.

    Das passiert normalerweise mit einer spärlichen Datei. Sie können es reproduzieren mit:

    truncate -s200G some-file
    grep foo some-file
    

    Das ist schwer zu umgehen. Du könntest es so machen (immer noch mit GNU grep):

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

    Das konvertiert Sequenzen von NUL-Zeichen in ein Newline-Zeichen, bevor die Eingabe an übergeben wird grep. Dies gilt für Fälle, in denen das Problem auf spärliche Dateien zurückzuführen ist.

    Sie können es optimieren, indem Sie es nur für große Dateien ausführen:

    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 {} + \)
    

    Wenn die Dateien nicht spärlich , und Sie haben eine Version von GNU grepvor 2.6, können Sie die Verwendung --mmapOption. Die Zeilen werden nicht kopiert, sondern im Speicher abgelegt. Das bedeutet, dass das System den Speicher jederzeit durch Auslagern der Seiten in die Datei freischalten kann. Diese Option wurde in GNU grep2.6 entfernt

Stéphane Chazelas
quelle
Tatsächlich kümmert sich GNU grep nicht darum, eine Zeile einzulesen, sondern liest einen großen Teil der Datei in einen einzelnen Puffer. "Außerdem vermeidet GNU grep das Brechen der Eingabe in Zeilen." Quelle: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer
4
@GodricSeer, es kann immer noch einen großen Teil der Datei in einen einzelnen Puffer lesen, aber wenn es die Zeichenfolge dort nicht findet und auch kein Zeilenumbruchzeichen gefunden hat, ist meine Wette, dass es diesen einzelnen Puffer im Speicher behält und liest den nächsten Puffer ein, da dieser angezeigt werden muss, wenn eine Übereinstimmung gefunden wird. Das Problem ist also immer noch dasselbe. In der Praxis schlägt ein Grep auf einer 200-GB-Sparse-Datei mit OOM fehl.
Stéphane Chazelas
1
@GodricSeer, nun nein. Wenn alle Zeilen klein sind, grepkönnen die bisher verarbeiteten Puffer verworfen werden. Sie können grepdie Ausgabe auf yesunbestimmte Zeit ausführen, ohne mehr als ein paar Kilobyte Speicher zu belegen. Das Problem ist die Größe der Linien.
Stéphane Chazelas
3
Die GNU grep --null-dataOption kann auch hier nützlich sein. Erzwingt die Verwendung von NUL anstelle von Newline als Eingabezeilenabschluss.
Iruvar
1
@ 1_CR, guter Punkt, aber das setzt auch den Ausgangsleitungsabschluss auf NUL.
Stéphane Chazelas
5

Normalerweise mache ich

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

Ich habe eine Reihe von Methoden ausprobiert und fand, dass dies die schnellste ist. Beachten Sie, dass dies Dateien mit Leerzeichen, die den Dateinamen enthalten, nicht sehr gut behandelt. Wenn Sie wissen, dass dies der Fall ist und eine GNU-Version von grep haben, können Sie Folgendes verwenden:

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

Wenn nicht, können Sie Folgendes verwenden:

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

Welches wird execein Grep für jede Datei.

Kotte
quelle
Dies wird bei Dateien mit Leerzeichen unterbrochen.
Chris Down
Hmm, das stimmt.
Kotte,
Sie können das find -print0 | xargs -0 grep -ne 'expression'
umgehen
@ChrisDown ist eher eine nicht protierbare Lösung als eine kaputte tragbare Lösung.
Reto
@ChrisDown Die meisten großen Unices haben find -print0und xargs -0bis jetzt angenommen: alle drei BSD, MINIX 3, Solaris 11, ...
Gilles 'SO - hör auf, böse zu sein'
4

Ich kann mir ein paar Möglichkeiten vorstellen, um das zu umgehen:

  • Anstatt alle Dateien auf einmal zu prüfen, sollten Sie immer nur eine Datei gleichzeitig ausführen. Beispiel:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Wenn Sie nur wissen müssen, welche Dateien die Wörter enthalten, tun Sie dies grep -lstattdessen. Da grep dort nach dem ersten Treffer aufhört zu suchen, muss es keine riesigen Dateien mehr lesen

  • Wenn Sie auch den eigentlichen Text haben möchten, können Sie zwei separate Greps aneinanderreihen:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    
Jenny D
quelle
Das letzte Beispiel ist keine gültige Syntax - Sie müssen einen Befehl ersetzen (und das sollten Sie auch nicht tun, da für die grepAusgabe ein Begrenzer verwendet wird, der in Dateinamen zulässig ist). Sie müssen auch zitieren $file.
Chris Down
Das letztgenannte Beispiel leidet unter dem Problem, dass Dateinamen Newline- oder Whitespace-Zeichen enthalten (dies führt dazu for, dass die Datei als zwei Argumente verarbeitet wird)
Drav Sloan,
@DravSloan Während Ihre Bearbeitung eine Verbesserung darstellt, werden die zulässigen Dateinamen immer noch verletzt.
Chris Down
1
Ja, ich habe es in gelassen, weil es Teil ihrer Antwort war. Ich habe nur versucht, es zu verbessern, damit es ausgeführt werden kann (für den Fall, dass keine Leerzeichen / Zeilenumbrüche usw. in Dateien vorhanden sind).
Drav Sloan
Korrekturen von ihm -> ihr, ich entschuldige mich Jenny: /
Drav Sloan
1

Ich greife nach einer 6-TB-Festplatte, um nach verlorenen Daten zu suchen, und der Speicher ist erschöpft. Dies sollte auch für andere Dateien funktionieren.

Die Lösung, die wir gefunden haben, bestand darin, die Festplatte mit dd in Chunks zu lesen und die Chunks zu sperren. Dies ist der 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
quelle
1
Wenn Sie nicht überlappende Abschnitte lesen , werden Sie möglicherweise Übereinstimmungen an den Abschnittsgrenzen verpassen. Die Überlappung muss mindestens so groß sein wie die zu erwartende Zeichenfolge.
Kusalananda
Aktualisiert, um in jedem 100-
MB-Block zusätzlich