grep: pamięć wyczerpana

42

Przeprowadziłem bardzo proste wyszukiwanie:

grep -R Milledgeville ~/Documents

Po pewnym czasie pojawił się ten błąd:

grep: memory exhausted

Jak mogę tego uniknąć?

Mam 10 GB pamięci RAM w moim systemie i działa kilka aplikacji, więc jestem naprawdę zaskoczony, że w prostym grep zabrakło pamięci. ~/Documentsma około 100 GB i zawiera wszystkie rodzaje plików.

grep -RI może nie mieć tego problemu, ale chcę również wyszukiwać pliki binarne.

Nicolas Raoul
źródło

Odpowiedzi:

46

Dwa potencjalne problemy:

  • grep -R(z wyjątkiem zmodyfikowanego GNU grepznajdującego się w OS / X 10.8 i nowszych) podąża za dowiązaniami symbolicznymi, więc nawet jeśli jest tam tylko 100 GB plików ~/Documents, może być /na przykład dowiązanie symboliczne i skończysz skanować cały system plików łącznie z plikami jak /dev/zero. Użyj grep -rz nowszym GNU greplub użyj standardowej składni:

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

    (należy jednak pamiętać, że status wyjścia nie odzwierciedla faktu, że wzorzec jest dopasowany, czy nie).

  • grepznajduje linie pasujące do wzorca. W tym celu musi ładować jedną linię w pamięci. GNU, grepw przeciwieństwie do wielu innych grepimplementacji, nie ma ograniczenia rozmiaru linii, które czyta i obsługuje wyszukiwanie w plikach binarnych. Tak więc, jeśli masz plik z bardzo dużą linią (czyli dwiema znakami nowej linii bardzo daleko), większy niż dostępna pamięć, to się nie powiedzie.

    Zwykle dzieje się tak z rzadkim plikiem. Możesz go odtworzyć za pomocą:

    truncate -s200G some-file
    grep foo some-file
    

    Tego trudno obejść. Możesz to zrobić jako (wciąż z GNU grep):

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

    Konwertuje sekwencje znaków NUL na jeden znak nowej linii przed wprowadzeniem danych wejściowych grep. Obejmuje to przypadki, w których problem wynika z rzadkich plików.

    Możesz to zoptymalizować, robiąc to tylko dla dużych plików:

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

    Jeśli pliki są nie rzadki i masz wersję GNU grepprzed 2.6, można użyć --mmapopcji. Linie zostaną zmapowane w pamięci w przeciwieństwie do tam skopiowanych, co oznacza, że ​​system zawsze może odzyskać pamięć, stronicując strony do pliku. Ta opcja została usunięta w GNU grep2.6

Stéphane Chazelas
źródło
W rzeczywistości GNU grep nie dba o czytanie w 1 linii, odczytuje dużą część pliku w jednym buforze. „Ponadto GNU grep UNIKA PRZERWANIA WEJŚCIA W LINIE”. źródło: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer
4
@GodricSeer, może nadal czytać dużą część pliku w jednym buforze, ale jeśli nie znalazł tam łańcucha i nie znalazł znaku nowej linii, założę się, że zachowuje ten pojedynczy bufor w pamięci i wczytuje następny bufor, ponieważ będzie musiał go wyświetlić, jeśli zostanie znalezione dopasowanie. Problem jest nadal ten sam. W praktyce grep w pliku rzadkim o pojemności 200 GB nie działa z OOM.
Stéphane Chazelas,
1
@GodricSeer, no no. Jeśli wszystkie linie są małe, grepmogą odrzucić bufory, które do tej pory przetworzyły. Możesz grepgenerować dane w yesnieskończoność bez użycia więcej niż kilku kilobajtów pamięci. Problemem jest rozmiar linii.
Stéphane Chazelas,
3
--null-dataPrzydaje się również opcja grep GNU . Wymusza użycie NUL zamiast nowej linii jako terminatora linii wejściowej.
iruvar
1
@ 1_CR, dobra uwaga, ale to także ustawia terminator linii wyjściowej na NUL.
Stéphane Chazelas,
5

Zwyklę robię

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

Wypróbowałem kilka metod i okazało się, że jest to najszybszy. Zauważ, że nie radzi sobie to dobrze z plikami ze spacjami o nazwie pliku. Jeśli wiesz, że tak jest i masz wersję grep GNU, możesz użyć:

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

Jeśli nie, możesz użyć:

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

Który będzie execgrep dla każdego pliku.

Kotte
źródło
Spowoduje to uszkodzenie plików ze spacjami.
Chris Down,
Hmm, to prawda.
Kotte,
Można to obejść za pomocąfind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan
@ChrisDown raczej rozwiązanie nie do ochrony niż rozwiązanie zepsute-przenośne.
reto
@ChrisDown Większość dużych jednorożców przyjęła find -print0i xargs -0do tej pory: wszystkie trzy BSD, MINIX 3, Solaris 11,…
Gilles „SO- przestań być zły”
4

Mogę wymyślić kilka sposobów na obejście tego:

  • Zamiast grepowania wszystkich plików naraz, zrób jeden plik na raz. Przykład:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Jeśli potrzebujesz tylko wiedzieć, które pliki zawierają słowa, zrób to grep -l. Ponieważ grep przestanie szukać po pierwszym trafieniu, nie będzie musiał czytać żadnych dużych plików

  • Jeśli chcesz również rzeczywisty tekst, możesz napisać dwa osobne grep wzdłuż:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    
Jenny D.
źródło
Ostatni przykład jest niepoprawną składnią - należy wykonać podstawienie polecenia (i nie należy tego robić, ponieważ dane grepwyjściowe używają separatora zgodnego z nazwami plików). Musisz także zacytować $file.
Chris Down,
Ten ostatni przykład cierpi na problem nazw plików zawierających znak nowej linii lub białe znaki (spowoduje forto przetworzenie pliku jako dwóch argumentów)
Drav Sloan
@DravSloan Twoja zmiana, mimo że jest poprawiona, wciąż psuje legalne nazwy plików.
Chris Down,
1
Tak, zostawiłem to, ponieważ była to część jej odpowiedzi, po prostu próbowałem ją poprawić, aby działała (w przypadkach, gdy w plikach nie ma spacji / znaków nowej linii).
Drav Sloan,
Korekty jego -> jej, moje przeprosiny Jenny: /
Drav Sloan 10.09.2013
1

Chwytam dysk o pojemności 6 TB, aby wyszukać utracone dane, i wyczerpałem pamięć - błąd. To powinno działać również dla innych plików.

Rozwiązaniem, które wymyśliliśmy, było odczytanie dysku w porcjach za pomocą dd i grep. To jest kod (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
źródło
1
Chyba że czytaj nakładających się fragmentów, byś ewentualnie przegap mecze na granicach fragmentach. Nakładanie się musi być co najmniej tak duże, jak ciąg, który chcesz dopasować.
Kusalananda
Zaktualizowano, aby wyszukać 1 MB więcej w każdym 100 MB kawałku ... tani hack
Dagelf