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


42

Я робив дуже простий пошук:

grep -R Milledgeville ~/Documents

Через деякий час з’явилася ця помилка:

grep: memory exhausted

Як я можу цього уникнути?

У мене в системі 10 Гб оперативної пам’яті та кілька запущених додатків, тому я дуже здивований, що у простого грепа не вистачає пам’яті. ~/Documentsстановить близько 100 Гб і містить всі види файлів.

grep -RI ця проблема може не мати, але я хочу також шукати і у двійкових файлах.

Відповіді:


46

Дві потенційні проблеми:

  • grep -R(за винятком модифікованого GNU, grepзнайденого в OS / X 10.8 і вище), слід за символьними посиланнями, тому навіть якщо в ньому є лише 100 ГБ файлів ~/Documents, можливо, все-таки буде посилання на, /наприклад, і ви закінчите сканувати всю файлову систему, включаючи файли як /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 ВНИМАТИ ВРУШЕННЯ ВХОДУ В ЛІНІ." Джерело: list.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer

4
@GodricSeer, він все ще може прочитати велику частину файлу в одному буфері, але якщо він не знайшов рядок і не знайшов символу нового рядка, я думаю, що він зберігає цей єдиний буфер в пам'яті і читає наступний буфер в, оскільки він повинен буде відобразити його, якщо буде знайдено відповідність. Отже, проблема все одно. На практиці помилка на розрідженому файлі об'ємом 200 Гб не працює з OOM.
Стефан Шазелас

1
@GodricSeer, ну ні. Якщо рядки невеликі, grepможна відкинути буфери, які він обробляв дотепер. Ви можете grepвиводити yesнескінченно довго, не використовуючи більше кількох кілобайт пам'яті. Проблема - розмір ліній.
Стефан Шазелас

3
Тут --null-dataтакож може бути корисний варіант GNU grep . Це змушує використовувати NUL замість newline як термінатор вхідної лінії.
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' "{}" \;

Що буде execгрепкою для кожного файлу.


Це порушить файли з пробілами.
Кріс Даун

Хм, це правда.
Котте

Ви можете find -print0 | xargs -0 grep -ne 'expression'
обійтись цим

@ChrisDown - це скоріше непрограмне рішення, ніж розроблене портативне рішення.
рето

@ChrisDown Більшість основних уніцій прийняли find -print0і xargs -0до цього часу: всі три BSD, MINIX 3, Solaris 11,…
Жил 'SO- перестань бути злом'

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
Так, я залишив це, оскільки це було частиною її відповіді, я просто намагався вдосконалити його, щоб він працював (для випадків, коли у файлах немає пробілів / нових рядків тощо).
Drav Sloan

Виправлення його -> її, мої вибачення Дженні: /
Drav Sloan

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
Якщо ви не читаєте, що перекриваються шматками, ви, можливо, пропустите сірники на кордонах. Перекриття має бути принаймні таким же великим, як і рядок, яку ви очікуєте співпадати.
Kusalananda

Оновлено, щоб шукати 1 Мб додатково на кожен шматок по 100 Мб ... дешевий злом
Dagelf
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.