grep: память исчерпана


42

Я делал очень простой поиск:

grep -R Milledgeville ~/Documents

И через некоторое время появилась эта ошибка:

grep: memory exhausted

Как я могу избежать этого?

У меня в системе 10 ГБ ОЗУ и запущено несколько приложений, поэтому я очень удивлен, что у простого grep не хватает памяти. ~/Documentsсоставляет около 100 ГБ и содержит все виды файлов.

grep -RI может не иметь этой проблемы, но я хочу искать в двоичных файлах тоже.

Ответы:


46

Две потенциальные проблемы:

  • grep -R(за исключением модифицированного GNU, grepнайденного в OS / X 10.8 и выше), следует ~/Documentsсимвольные ссылки , поэтому, даже если в нем всего 100 ГБ файлов , может существовать символическая ссылка, /например, и вы в конечном итоге сканируете всю файловую систему, включая файлы как /dev/zero. Используйте grep -rс более новым GNU grep, или используйте стандартный синтаксис:

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

    (однако обратите внимание, что статус выхода не будет отражать тот факт, что шаблон соответствует или нет).

  • grepнаходит линии, которые соответствуют шаблону. Для этого он должен загружать одну строку за раз в памяти. GNU, grepв отличие от многих других grepреализаций, не имеет ограничения на размер строк, которые он читает, и поддерживает поиск в двоичных файлах. Таким образом, если у вас есть файл с очень большой строкой (то есть с двумя символами новой строки, расположенными очень далеко), больше, чем доступная память, он потерпит неудачу.

    Это обычно происходит с разреженным файлом. Вы можете воспроизвести это с:

    truncate -s200G some-file
    grep foo some-file
    

    Это трудно обойти. Вы можете сделать это как (все еще с GNU grep):

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

    Это преобразует последовательности символов NUL в один символ новой строки перед подачей ввода в grep. Это касается случаев, когда проблема связана с редкими файлами.

    Вы можете оптимизировать это, делая это только для больших файлов:

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

    Если файлы не редки, и у вас есть версия GNU grepдо 2.6, вы можете использовать эту --mmapопцию. Строки будут отображаться в памяти, а не копироваться туда, что означает, что система всегда может восстановить память, перемещая страницы в файл. Эта опция была удалена в GNU grep2.6


На самом деле GNU grep не заботится о чтении в 1 строку, он читает большую часть файла в один буфер. «Кроме того, GNU grep ИЗБЕГАЕТ РАЗРЫВАТЬ ВХОД В ЛИНИИ». источник: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
— Годрик Провидец

4
@GodricSeer, он все еще может считывать большую часть файла в один буфер, но если он не нашел там строку и не нашел символа новой строки, я уверен, что он хранит этот единственный буфер в памяти и читает следующий буфер, так как он должен будет отобразить его, если найдено совпадение. Итак, проблема все та же. На практике grep для файла размером 200 ГБ не работает с OOM.
— Стефан Шазелас

1
@GodricSeer, ну нет. Если строки все маленькие, grepможно отбросить буферы, которые он обработал до сих пор. Вы можете grepвыводить на yesнеопределенный срок без использования более нескольких килобайт памяти. Проблема заключается в размере линий.
— Стефан Шазелас

3
Здесь --null-dataтакже может быть полезна опция GNU grep . Это заставляет использовать NUL вместо новой строки в качестве ограничителя входной строки.
— iruvar

1
@ 1_CR, хорошая точка, хотя это также устанавливает терминатор выходной строки в NUL.
— Стефан Шазелас

5

Я обычно делаю

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

Я попробовал кучу методов, и нашел, что это самый быстрый. Обратите внимание, что это не очень хорошо обрабатывает файлы с пробелами в имени файла. Если вы знаете, что это так, и имеете GNU-версию grep, вы можете использовать:

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

Если нет, вы можете использовать:

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

Который будет execgrep для каждого файла.


Это сломает файлы с пробелами.
— Крис Даун

Хм, это правда.
— Котт

Вы можете обойти это сfind -print0 | xargs -0 grep -ne 'expression'
— Драв Слоан

@ChrisDown скорее непереносимое решение, чем сломанное-переносимое решение.
— 13

@ChrisDown Большинство крупных объединений приняли find -print0и xargs -0к настоящему моменту: все три BSD, MINIX 3, Solaris 11,…
— Жиль «ТАК - перестать быть злым»

4

Я могу придумать несколько способов обойти это:

  • Вместо того, чтобы подбирать все файлы одновременно, делайте один файл за раз. Пример:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Если вам нужно только знать, какие файлы содержат слова, сделайте grep -lвместо этого. Поскольку grep прекратит поиск после первого попадания, ему не нужно будет продолжать читать огромные файлы.

  • Если вы также хотите получить реальный текст, вы можете связать две отдельные команды:

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

В последнем примере неверный синтаксис - вам нужно выполнить подстановку команд (и вы не должны этого делать, поскольку в grepвыходных данных используется разделитель, допустимый в именах файлов). Вы также должны цитировать $file.
— Крис Даун

В последнем примере возникает проблема с именами файлов, в которых есть символы новой строки или пробелы (это приведет forк обработке файла как двух аргументов)
— Drav Sloan

@DravSloan Ваше редактирование, хотя и является улучшением, все еще нарушает допустимые имена файлов.
— Крис Даун

1
Да, я оставил это, потому что это было частью ее ответа, я просто попытался улучшить его, чтобы он работал (для случаев, когда в файлах нет пробелов / переносов и т. Д.).
— Драв Слоан

Исправления его -> ее, мои извинения Дженни: /
— Драв Слоан

1

Я копирую диск размером 6 ТБ для поиска потерянных данных и получаю исчерпанную память - ошибка. Это должно работать и для других файлов.

Решение, которое мы придумали, состояло в том, чтобы читать диск кусками, используя dd, и подбирать куски. Это код (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

1
Если вы не читаете перекрывающиеся фрагменты, вы можете пропустить совпадения на границах фрагментов. Перекрытие должно быть по крайней мере таким же большим, как строка, которую вы ожидаете найти.
— Кусалананда

Обновлен поиск дополнительных 1 МБ в каждом
— блоке размером
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.