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
PHZ.fi-Фаразон
источник
1
Если вы не читаете перекрывающиеся фрагменты, вы можете пропустить совпадения на границах фрагментов. Перекрытие должно быть по крайней мере таким же большим, как строка, которую вы ожидаете найти.
Кусалананда
Обновлен поиск дополнительных 1 МБ в каждом
блоке размером