Jag gjorde en mycket enkel sökning:

grep -R Milledgeville ~/Documents 

Och efter en tid uppstod detta fel:

grep: memory exhausted 

Hur kan jag undvika detta?

Jag har 10 GB RAM på mitt system och få applikationer kör, så jag är verkligen förvånad över att en enkel grep tar slut på minne. ~/Documents är ungefär 100 GB och innehåller alla typer av filer.

grep -RI kanske inte har det här problemet, men jag vill att söka i binära filer också.

Svar

Två potentiella problem:

  • grep -R (förutom den modifierade GNU grep som finns på OS / X 10.8 och senare) följer symlänkar, så även om det bara finns 100 GB filer i ~/Documents, det kan fortfarande finnas en symlänk till / till exempel och du kommer att sluta skanna hela filsystemet inklusive filer som /dev/zero. Använd grep -r med nyare GNU grep, eller använd standardsyntaxen:

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

    (dock observera att utgångsstatusen inte återspeglar det faktum att mönstret matchas eller inte).

  • grep hittar raderna som matchar mönstret. För det måste den ladda en rad i taget i minnet. GNU grep i motsats till många andra grep -implementeringar har inte en gräns för storleken på raderna som den läser och stöder sökning i binära filer. Så om du har en fil med en mycket stor rad (det vill säga med två nylinjetecken mycket långt), större än tillgängligt minne, kommer den att misslyckas.

    Det skulle vanligtvis hända med en gles fil. Du kan reproducera den med:

    truncate -s200G some-file grep foo some-file 

    Den här är svår att komma runt. Du kan göra det som (fortfarande 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 omvandlar sekvenser av NUL-tecken till ett nytt linjetecken innan ingången matas till grep. Det skulle täcka för fall där problemet beror på glesa filer.

    Du kan optimera det genom att bara göra det för stora 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 {} + \) 

    Om filerna är inte glesa och du har en version av GNU grep före 2.6, du kan använda alternativet --mmap. Raderna mappas i minnet i motsats till kopieras där, vilket innebär att systemet alltid kan återta minnet y genom att söka ut sidorna till filen. Det alternativet togs bort i GNU grep 2.6

Kommentarer

  • @GodricSeer kan det fortfarande läsa en stor del av filen i en enda buffert, men om den inte ’ inte hittar strängen där och inte har ’ Hittade inte heller en nylinjetecken, min insats är att den håller den enskilda bufferten i minnet och läser nästa buffert i, eftersom den måste visa den om en matchning hittas. Så problemet är fortfarande detsamma. I praktiken misslyckas en grep på en 200 GB sparsam fil med OOM.
  • @GodricSeer, ja nej. Om raderna alla är små kan grep kasta buffertarna som den har bearbetat hittills. Du kan grep utdata från yes på obestämd tid utan att använda mer än några kilobyte minne. Problemet är storleken på raderna.
  • Alternativet GNU grep --null-data kan också vara användbart här. Det tvingar användningen av NUL istället för newline som en ingångsledningsavslutare.
  • @ 1_CR, bra punkt, men det sätter också utgångsledningsavslutaren till NUL.
  • Skulle fold kommandohjälp i dessa situationer? Tänk till exempel på dd if=/dev/sda | fold -b $((4096*1024*1024)) | grep -a "some string" för att begränsa mängden minne som krävs till 4 GB

Svar

Jag brukar göra

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

Jag försökte en massa metoder och fann att det här var det snabbaste. Observera att detta inte hanterar filer med mellanslag filnamnet mycket bra. Om du vet att detta är fallet och har en GNU-version av grep kan du använda:

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

Om inte kan du använda:

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

Vilket kommer exec en grep för varje fil.

Kommentarer

  • Detta kommer att bryta på filer med mellanslag.
  • Hmm, det är sant.
  • Du kan komma runt det med find -print0 | xargs -0 grep -ne 'expression'
  • @ChrisDown snarare en icke-skyddbar lösning än en trasig bärbar lösning.
  • @ChrisDown Most stora enheter har antagit find -print0 och xargs -0 nu: alla tre BSD, MINIX 3, Solaris 11,…

Svar

Jag kan tänka mig några sätt att komma runt detta:

  • Istället för att greppa alla filer samtidigt, gör en fil i taget.Exempel:

    find /Documents -type f -exec grep -H Milledgeville "{}" \; 
  • Om du bara behöver veta vilka filer som innehåller orden, gör grep -l istället. Eftersom grep slutar söka efter den första träffen, behöver den inte fortsätta läsa några enorma filer

  • Om du också vill ha den faktiska texten kan du stränga två separata greps längs:

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

Kommentarer

  • Det sista exemplet är inte giltig syntax – du ’ d behöver utföra ett kommandosubstitution (och du bör inte ’ t göra det, eftersom grep utdata med en avgränsare som är laglig i filnamn). Du måste också citera $file.
  • Det senare exemplet lider med frågan om filnamn som innehåller ny linje eller tomt utrymme, (det kommer att göra att for bearbetar filen som två argument)
  • @DravSloan Din redigering, medan en förbättring, bryter fortfarande med lagliga filnamn.
  • Ja, jag lämnade det eftersom det var en del av hennes svar, jag försökte bara förbättra det så att det skulle gå (för de fall där jag s inga mellanslag / nya rader etc i filer).
  • Korrigeringar av hans – > henne, min ursäkt Jenny: /

Svar

Jag tar en 6TB-disk för att söka efter förlorade data och fick minnet uttömt -fel. Detta bör också fungera för andra filer.

Lösningen vi kom fram till var att läsa disken i bitar genom att använda dd och greppa bitarna. Det här är 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

  • Om du inte läser överlappande bitar, du skulle eventuellt missa matcher på bitgränserna. Överlappningen måste vara minst lika stor som strängen som du förväntar dig att matcha.
  • Uppdaterad för att söka 1 MB extra i varje 100 MB bit … billigt hack

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *