Jeg foretog en meget enkel søgning:

grep -R Milledgeville ~/Documents 

Og efter nogen tid dukkede denne fejl op:

grep: memory exhausted 

Hvordan kan jeg undgå dette?

Jeg har 10 GB RAM på mit system og få applikationer kører, så jeg er virkelig overrasket over, at en simpel grep løber tør for hukommelse. ~/Documents handler om 100 GB og indeholder alle slags filer.

grep -RI har muligvis ikke dette problem, men jeg vil have for at søge i binære filer også.

Svar

To potentielle problemer:

  • grep -R (undtagen den ændrede GNU grep fundet på OS / X 10.8 og derover) følger symlinks, så selvom der kun er 100 GB filer i ~/Documents, der er muligvis stadig et symlink til / for eksempel, og du vil ende med at scanne hele filsystemet inklusive filer som /dev/zero. Brug grep -r med nyere GNU grep, eller brug standardsyntaks:

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

    (dog bemærk, at udgangsstatus ikke afspejler det faktum, at mønsteret er matchet eller ikke).

  • grep finder de linjer, der matcher mønsteret. For det skal den indlæse en linje ad gangen i hukommelsen. GNU grep i modsætning til mange andre grep implementeringer har ikke en grænse for størrelsen på de linjer, den læser og understøtter søgning i binære filer. Så hvis du har en fil med en meget stor linje (dvs. med to newline-tegn meget langt), større end den tilgængelige hukommelse, vil den mislykkes.

    Det ville typisk ske med en sparsom fil. Du kan gengive den med:

    truncate -s200G some-file grep foo some-file 

    Den ene er vanskelig at omgå. Du kan gøre det som (stadig 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 {} + 

    Det konverterer sekvenser af NUL-tegn til et nyt linjetegn, før input tilføres til grep. Dette dækker tilfælde, hvor problemet skyldes sparsomme filer.

    Du kan optimere det ved kun at gøre det 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 filerne ikke er sparsomme, og du har en version af GNU grep før 2.6, du kan bruge indstillingen --mmap. Linjerne kortlægges i hukommelsen i modsætning til kopieret der, hvilket betyder, at systemet altid kan genvinde memoren y ved at page siderne ud til filen. Denne mulighed blev fjernet i GNU grep 2.6

Kommentarer

  • @GodricSeer, det kan stadig læse en stor del af filen i en enkelt buffer, men hvis den ikke har ‘ ikke finder strengen derinde og ikke har ‘ fandt heller ikke en newline-karakter, min indsats er, at den holder den enkelte buffer i hukommelsen og læser den næste buffer i, da den bliver nødt til at vise den, hvis der findes et match. Så problemet er stadig det samme. I praksis mislykkes en grep på en 200 GB sparsom fil med OOM.
  • @GodricSeer, godt nej. Hvis linjer alle er små, kan grep kassere de buffere, den hidtil har behandlet. Du kan grep output fra yes på ubestemt tid uden at bruge mere end et par kilobyte hukommelse. Problemet er størrelsen på linjerne.
  • GNU grep --null-data -indstillingen kan også være nyttig her. Det tvinger brugen af NUL i stedet for newline som en input line terminator.
  • @ 1_CR, godt punkt, selvom det også sætter output line terminatoren til NUL.
  • Ville fold kommandohjælp i disse situationer? Tænk f.eks. På dd if=/dev/sda | fold -b $((4096*1024*1024)) | grep -a "some string" for at begrænse den krævede hukommelse til 4 GB

Svar

Jeg plejer at gøre

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

Jeg prøvede en række metoder og fandt dette at være den hurtigste. Bemærk, at dette ikke håndterer filer med mellemrum filnavnet meget godt. Hvis du ved, at dette er tilfældet og har en GNU-version af grep, kan du bruge:

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

Hvis ikke, kan du bruge:

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

Hvilket exec en grep for hver fil.

Kommentarer

  • Dette bryder filer på mellemrum.
  • Hmm, det er sandt.
  • Du kan omgå det med find -print0 | xargs -0 grep -ne 'expression'
  • @ChrisDown snarere en ikke-beskyttende løsning end en brudt bærbar løsning.
  • @ChrisDown Most store enheder har vedtaget find -print0 og xargs -0 nu: alle tre BSD, MINIX 3, Solaris 11,…

Svar

Jeg kan tænke på et par måder at komme rundt på dette:

  • I stedet for at hente alle filer på én gang skal du lave en fil ad gangen.Eksempel:

    find /Documents -type f -exec grep -H Milledgeville "{}" \; 
  • Hvis du kun behøver at vide, hvilke filer der indeholder ordene, skal du gøre grep -l i stedet. Da grep stopper med at søge efter det første hit, behøver det ikke at fortsætte med at læse store filer

  • Hvis du også vil have den faktiske tekst, kan du streng to adskil greps langs:

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

Kommentarer

  • Det sidste eksempel er ikke gyldig syntaks – du ‘ d skal udføre en kommandosubstitution (og du skal ikke ‘ ikke gøre det, da grep output ved hjælp af en afgrænser, der er lovlig i filnavne). Du skal også citere $file.
  • Sidstnævnte eksempel lider med spørgsmålet om filnavne med ny linje eller mellemrum i dem (det vil få for til at behandle filen som to argumenter)
  • @DravSloan Din redigering, mens en forbedring, stadig bryder juridiske filnavne.
  • Ja, jeg lod det være, fordi det var en del af hendes svar, jeg prøvede bare at forbedre det, så det ville køre (i de tilfælde hvor jeg s ingen mellemrum / nye linjer osv. i filer).
  • Rettelser af hans – > hende, jeg undskylder Jenny: /

Svar

Jeg griber en 6 TB disk for at søge efter mistede data og fik hukommelsen opbrugt -fejl. Dette burde også fungere for andre filer.

Den løsning, vi kom op med, var at læse disken i stykker ved hjælp af dd og gribe klumperne. 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

  • Medmindre du læser overlappende klumper, vil du muligvis gå glip af kampe på klodsens grænser. Overlappingen skal være mindst lige så stor som den streng, du forventer at matche.
  • Opdateret til at søge 1 MB ekstra i hver 100 MB klump … billig hack

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *