grep: memoria esaurita


42

Stavo facendo una ricerca molto semplice:

grep -R Milledgeville ~/Documents

E dopo qualche tempo è apparso questo errore:

grep: memory exhausted

Come posso evitarlo?

Ho 10 GB di RAM sul mio sistema e poche applicazioni sono in esecuzione, quindi sono davvero sorpreso che un semplice grep abbia esaurito la memoria. ~/Documentsè di circa 100 GB e contiene tutti i tipi di file.

grep -RI potrebbe non avere questo problema, ma voglio cercare anche nei file binari.

Risposte:


46

Due potenziali problemi:

  • grep -R(ad eccezione della GNU modificata greptrovata su OS / X 10.8 e versioni successive) segue i collegamenti simbolici, quindi anche se ci sono solo 100 GB di file ~/Documents, potrebbe esserci ancora un collegamento simbolico ad /esempio e finirai per scansionare l'intero file system inclusi i file come /dev/zero. Utilizzare grep -rcon GNU più recenti grepo utilizzare la sintassi standard:

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

    (tuttavia si noti che lo stato di uscita non rifletterà il fatto che il modello sia abbinato o meno).

  • greptrova le linee che corrispondono al modello. Per questo, deve caricare una riga alla volta in memoria. GNU, grepa differenza di molte altre grepimplementazioni, non ha un limite alla dimensione delle righe che legge e supporta la ricerca in file binari. Quindi, se hai un file con una linea molto grande (cioè con due caratteri newline molto lontani), più grande della memoria disponibile, fallirà.

    Ciò accade in genere con un file sparse. Puoi riprodurlo con:

    truncate -s200G some-file
    grep foo some-file
    

    Quello è difficile da aggirare. Potresti farlo come (sempre 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 {} +
    

    Ciò converte le sequenze di caratteri NUL in un carattere di nuova riga prima di alimentare l'input grep. Ciò coprirebbe i casi in cui il problema è dovuto a file sparsi.

    Potresti ottimizzarlo facendolo solo per file di grandi dimensioni:

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

    Se i file sono non sparse e si dispone di una versione di GNU grepprima 2.6, è possibile utilizzare l' --mmapopzione. Le linee verranno mmappate in memoria anziché copiate lì, il che significa che il sistema può sempre recuperare la memoria pagando le pagine nel file. Questa opzione è stata rimossa in GNU grep2.6


In realtà, GNU grep non si preoccupa di leggere in 1 riga, legge gran parte del file in un singolo buffer. "Inoltre, GNU grep EVITA DI Rompere l'INGRESSO IN LINEE." fonte: lists.freebsd.org/pipermail/freebsd-current/2010-agosto/…
Godric Seer,

4
@GodricSeer, potrebbe ancora leggere gran parte del file in un singolo buffer, ma se non ha trovato la stringa e non ha trovato nemmeno un carattere di nuova riga, la mia scommessa è che mantiene quel singolo buffer in memoria e legge il buffer successivo in, poiché dovrà visualizzarlo se viene trovata una corrispondenza. Quindi, il problema è sempre lo stesso. In pratica, un grep su un file sparse da 200 GB non riesce con OOM.
Stéphane Chazelas,

1
@GodricSeer, beh no. Se le linee sono tutte piccole, è greppossibile eliminare i buffer che ha elaborato finora. È possibile grepprodurre in modo yesindefinito senza utilizzare più di pochi kilobyte di memoria. Il problema è la dimensione delle linee.
Stéphane Chazelas,

3
L' --null-dataopzione GNU grep può anche essere utile qui. Forza l'uso di NUL invece di newline come terminatore di linea di input.
Iruvar,

1
@ 1_CR, buon punto, anche se questo imposta anche il terminatore della linea di uscita su NUL.
Stéphane Chazelas,

5

Di solito lo faccio

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

Ho provato un sacco di metodi e ho scoperto che questo è il più veloce. Si noti che questo non gestisce molto bene i file con spazi il nome del file. Se sai che questo è il caso e hai una versione GNU di grep, puoi usare:

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

Altrimenti puoi usare:

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

Quale sarà execun grep per ogni file.


Ciò si interromperà sui file con spazi.
Chris Down,

Questo è vero.
Kotte,

Puoi aggirare il find -print0 | xargs -0 grep -ne 'expression'
problema

@ChrisDown piuttosto una soluzione non protettiva che una soluzione portatile rotta.
reto

@ChrisDown maggior parte dei principali sistemi Unix hanno adottato find -print0e xargs -0ormai: tutti e tre BSD, MINIX 3, Solaris 11, ...
Gilles 'SO-tappa è male'

4

Posso pensare ad alcuni modi per aggirare questo:

  • Invece di eseguire il grepping di tutti i file contemporaneamente, esegui un file alla volta. Esempio:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Se hai solo bisogno di sapere quali file contengono le parole, fallo grep -linvece. Poiché grep smetterà di cercare dopo il primo hit, non dovrà continuare a leggere file di grandi dimensioni

  • Se vuoi anche il testo reale, puoi mettere insieme due greps separati:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    

L'ultimo esempio non è una sintassi valida: dovresti eseguire una sostituzione di comando (e non dovresti farlo, poiché gli grepoutput utilizzano un delimitatore che è legale nei nomi dei file). Devi anche citare $file.
Chris Down,

Quest'ultimo esempio soffre del problema dei nomi di file che contengono newline o spazi bianchi (causerà forl'elaborazione del file come due argomenti)
Drav Sloan,

@DravSloan La tua modifica, pur migliorando, si interrompe ancora sui nomi di file legali.
Chris Down,

1
Sì, l'ho lasciato perché faceva parte della sua risposta, ho solo provato a migliorarlo in modo che funzionasse (per i casi in cui non ci sono spazi / righe o altro nei file).
Drav Sloan,

Correzioni sue -> lei, mi
scuso

1

Sto cercando un disco da 6 TB per cercare dati persi e ho esaurito la memoria -error. Questo dovrebbe funzionare anche per altri file.

La soluzione che ci è venuta in mente è stata quella di leggere il disco in blocchi usando dd e eseguendo il grepping dei blocchi. Questo è il codice (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

1
A meno che tu non legga pezzi sovrapposti , potresti perdere le partite sui limiti del pezzo. La sovrapposizione deve essere grande almeno quanto la stringa che ci si aspetta corrisponda.
Kusalananda

Aggiornato per cercare 1 MB in più in ogni blocco da
100
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.