Przeprowadziłem bardzo proste wyszukiwanie:
grep -R Milledgeville ~/Documents
I po jakimś czasie pojawił się ten błąd:
grep: memory exhausted
Jak mogę tego uniknąć?
Mam 10 GB pamięci RAM w systemie i kilka aplikacji działa, więc jestem naprawdę zaskoczony, że po prostym grepie zabraknie pamięci. ~/Documents
ma około 100 GB i zawiera wszystkie rodzaje plików.
grep -RI
może nie mieć tego problemu, ale chcę aby wyszukiwać w plikach binarnych.
Odpowiedź
Dwa potencjalne problemy:
-
grep -R
(z wyjątkiem zmodyfikowanego GNUgrep
znalezionego w systemie OS / X 10.8 i nowszych) podąża za linkami symbolicznymi, więc nawet jeśli są tylko 100 GB plików w~/Documents
, na przykład może nadal istnieć dowiązanie symboliczne do/
i ostatecznie przeskanujesz cały system plików, w tym pliki takie jak/dev/zero
. Użyjgrep -r
z nowszym GNUgrep
lub użyj standardowej składni:find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
(należy jednak pamiętać, że stan wyjścia nie odzwierciedla faktu, że wzorzec jest dopasowany lub nie).
-
grep
znajduje wiersze pasujące do wzorca. W tym celu musi ładować do pamięci po jednym wierszu. GNUgrep
w przeciwieństwie do wielu innychgrep
implementacje nie mają ograniczeń co do wielkości linii, które czyta i obsługują wyszukiwanie w plikach binarnych. Tak więc, jeśli masz plik z bardzo dużą linią (to znaczy z dwoma znakami nowej linii bardzo daleko od siebie), większym niż dostępna pamięć, zakończy się niepowodzeniem.Zwykle zdarza się to w przypadku rzadki plik. Możesz go odtworzyć za pomocą:
truncate -s200G some-file grep foo some-file
Ten jest trudny do obejścia. Możesz to zrobić jako (nadal 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 {} +
To konwertuje sekwencje znaków NUL na jeden znak nowej linii przed przekazaniem danych wejściowych do
grep
. Obejmuje to przypadki, w których problem jest spowodowany rzadkimi plikami.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 nie są rzadkie i masz wersję GNU
grep
przed2.6
, możesz użyć opcji--mmap
. Wiersze zostaną zapisane w pamięci zamiast kopiowanych, co oznacza, że system zawsze może odzyskać pamięć y, wysyłając strony do pliku. Ta opcja została usunięta w GNUgrep
2.6
Komentarze
Odpowiedź
Zwykle robię
find ~/Documents | xargs grep -ne "expression"
Wypróbowałem kilka metod i stwierdziłem, że jest to najszybsza. Zwróć uwagę, że nie obsługuje to plików ze spacjami nazwy pliku bardzo dobrze. Jeśli wiesz, że tak jest i masz wersję grep w wersji 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" "{}" \;
Co spowoduje exec
grep dla każdego pliku.
Komentarze
- Spowoduje to uszkodzenie plików ze spacjami.
- Hmm, to prawda.
- Możesz to obejść za pomocą
find -print0 | xargs -0 grep -ne 'expression'
- @ChrisDown, które jest raczej rozwiązaniem nieprzenośnym niż zepsutym rozwiązaniem przenośnym.
- @ChrisDown Most główne unices już przyjęły
find -print0
ixargs -0
: wszystkie trzy BSD, MINIX 3, Solaris 11,…
Odpowiedź
Mogę wymyślić kilka sposobów obejścia tego problemu:
-
Zamiast tego grepowania wszystkich plików naraz, ró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
grep -l
zamiast tego. Ponieważ grep przestanie tam 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 oddziel greps wzdłuż:
for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
Komentarze
- Ostatni przykład nie jest poprawną składnią – ' d musisz wykonać podstawienie polecenia (i nie powinieneś ' tego robić, ponieważ
grep
używa separatora, który jest dozwolony w nazwach plików). Musisz także zacytować$file
. - Ten ostatni przykład cierpi z problemem nazw plików zawierających znak nowej linii lub spacji (spowoduje to, że
for
przetworzy plik jako dwa argumenty) - @DravSloan Twoja edycja, podczas gdy ulepszenie, wciąż psuje się w legalnych nazwach plików.
- Tak, zostawiłem to, ponieważ była to część jej odpowiedzi, po prostu próbowałem to poprawić, aby działało (w przypadkach, nie ma spacji / znaków nowej linii itp. w plikach).
- Poprawki jego – > jej, przepraszam Jenny: /
Odpowiedź
I „m greping 6 TB dysku w celu wyszukania utraconych danych i wyczerpanie pamięci – błąd. To powinno działać również w przypadku innych plików.
Rozwiązaniem, które wymyśliliśmy, było odczytanie dysku w kawałkach za pomocą dd i grepowanie fragmentów. Oto 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
Komentarze
- Chyba że czytasz nakładające się fragmenty, prawdopodobnie przegapisz dopasowania na granicach fragmentów. Nakładka musi być co najmniej tak duża, jak ciąg, który chcesz dopasować.
- Zaktualizowano, aby wyszukiwać 1 MB więcej w każdym kawałku 100 MB … tani hack
grep
może odrzucić bufory, które przetworzył do tej pory. Możeszgrep
wyjścieyes
w nieskończoność bez użycia więcej niż kilku kilobajtów pamięci. Problemem jest rozmiar linii.--null-data
również może być tutaj przydatna. Wymusza użycie NUL zamiast nowej linii jako terminatora linii wejściowej.dd if=/dev/sda | fold -b $((4096*1024*1024)) | grep -a "some string"
, aby ograniczyć ilość wymaganej pamięci do 4 GB