Jeg gjorde et veldig enkelt søk:

grep -R Milledgeville ~/Documents 

Og etter en stund dukket denne feilen opp:

grep: memory exhausted 

Hvordan kan jeg unngå dette?

Jeg har 10 GB RAM på systemet og få applikasjoner kjører, så jeg er veldig overrasket over at en enkel grep går tom for minne. ~/Documents handler om 100 GB og inneholder alle slags filer.

grep -RI har kanskje ikke dette problemet, men jeg vil for å søke i binære filer også.

Svar

To potensielle problemer:

  • grep -R (bortsett fra den modifiserte GNU grep funnet på OS / X 10.8 og nyere) følger symlenker, så selv om det bare er 100 GB filer i ~/Documents, det kan fortsatt være en symlink til / for eksempel, og du vil ende opp med å skanne hele filsystemet inkludert filer som /dev/zero. Bruk grep -r med nyere GNU grep, eller bruk standardsyntaks:

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

    (men vær oppmerksom på at utgangsstatusen ikke gjenspeiler det faktum at mønsteret samsvarer eller ikke).

  • grep finner linjene som samsvarer med mønsteret. For det må den laste en linje av gangen i minnet. GNU grep i motsetning til mange andre grep implementeringer har ikke en grense for størrelsen på linjene den leser og støtter søk i binære filer. Så hvis du har en fil med en veldig stor linje (det vil si med to newline-tegn veldig langt fra hverandre), større enn tilgjengelig minne, vil den mislykkes.

    Det vil vanligvis skje med sparsom fil. Du kan reprodusere den med:

    truncate -s200G some-file grep foo some-file 

    Den ene er vanskelig å omgå. Du kan gjøre det som (fremdeles med GNU grep):

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

    Som konverterer sekvenser av NUL-tegn til ett nytt linjetegn før innmatingen mates til grep. Dette dekker tilfeller der problemet skyldes sparsomme filer.

    Du kan optimalisere det ved å gjøre det bare for store filer:

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

    Hvis filene er ikke sparsomme og du har en versjon av GNU grep før 2.6, du kan bruke alternativet --mmap. Linjene blir kartlagt i minnet i motsetning til kopiert der, noe som betyr at systemet alltid kan gjenvinne minnet y ved å søke ut sidene til filen. Dette alternativet ble fjernet i GNU grep 2.6

Kommentarer

  • @GodricSeer, det kan fremdeles lese en stor del av filen i en enkelt buffer, men hvis den ikke har ‘ ikke finner strengen der inne og ikke har ‘ fant heller ikke et nytt linjetegn, mitt veddemål er at den holder den eneste bufferen i minnet og leser den neste bufferen inn, da den må vise den hvis en kamp blir funnet. Så problemet er fortsatt det samme. I praksis mislykkes en grep på en 200 GB sparsom fil med OOM.
  • @GodricSeer, vel nei. Hvis linjene er små, kan grep kaste bufferne den har behandlet så langt. Du kan grep utgangen fra yes på ubestemt tid uten å bruke mer enn noen få kilobyte minne. Problemet er størrelsen på linjene.
  • Alternativet GNU grep --null-data kan også være nyttig her. Det tvinger bruken av NUL i stedet for newline som en inngangslinjeterminator.
  • @ 1_CR, godt poeng, skjønt som også setter utgangslinjeavslutningen til NUL.
  • Ville fold kommandohjelp i de situasjonene? Tenk for eksempel på dd if=/dev/sda | fold -b $((4096*1024*1024)) | grep -a "some string" for å begrense minnemengden som kreves til 4 GB

Svar

Jeg pleier å gjøre

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

Jeg prøvde en rekke metoder, og fant dette å være den raskeste. Merk at dette ikke håndterer filer med mellomrom filnavnet veldig bra. Hvis du vet at dette er tilfelle og har en GNU-versjon av grep, kan du bruke:

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

Hvis ikke, kan du bruke:

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

Hvilket vil exec en grep for hver fil.

Kommentarer

  • Dette vil bryte på filer med mellomrom.
  • Hmm, det stemmer.
  • Du kan komme deg rundt det med find -print0 | xargs -0 grep -ne 'expression'
  • @ChrisDown heller en ikke-beskyttende løsning enn en ødelagt bærbar løsning.
  • @ChrisDown Most store enheter har vedtatt find -print0 og xargs -0 nå: alle tre BSD, MINIX 3, Solaris 11,…

Svar

Jeg kan tenke meg noen måter å komme meg rundt:

  • I stedet å ta tak i alle filene samtidig, gjør en fil om gangen.Eksempel:

    find /Documents -type f -exec grep -H Milledgeville "{}" \; 
  • Hvis du bare trenger å vite hvilke filer som inneholder ordene, gjør du grep -l i stedet. Siden grep slutter å søke etter første treff, trenger den ikke å fortsette å lese store filer

  • Hvis du også vil ha den faktiske teksten, kan du streng to separate grep langs:

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

Kommentarer

  • Siste eksempel er ikke gyldig syntaks – du ‘ d trenger å utføre en kommandosubstitusjon (og du bør ikke ‘ t gjøre det, siden grep utganger ved hjelp av en avgrenser som er lovlig i filnavn). Du må også sitere $file.
  • Det siste eksemplet lider med problemet med filnavn som har ny linje eller mellomrom, (det vil føre til at for behandler filen som to argumenter)
  • @DravSloan Din redigering, mens en forbedring, bryter fremdeles med juridiske filnavn.
  • Ja, jeg la det være fordi det var en del av svaret hennes, jeg prøvde bare å forbedre det slik at det ville kjøre (for tilfeller er ingen mellomrom / nye linjer osv. i filer).
  • Rettelser av hans – > henne, jeg beklager Jenny: /

Svar

Jeg tar tak i en 6 TB disk for å søke etter tapte data, og fikk minnet utmattet -feil. Dette burde også fungere for andre filer.

Løsningen vi kom på var å lese disken i biter ved å bruke dd, og gripe biter. Dette er koden (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 

Kommentarer

  • Med mindre du leser overlappende biter, vil du muligens savne kamper på klumpegrensene. Overlappingen må være minst like stor som strengen du forventer å matche.
  • Oppdatert for å søke 1 MB ekstra i hver 100 MB del … billig hack

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *