grep: memori habis

42

Saya melakukan pencarian yang sangat sederhana:

grep -R Milledgeville ~/Documents

Dan setelah beberapa waktu kesalahan ini muncul:

grep: memory exhausted

Bagaimana saya bisa menghindari ini?

Saya memiliki 10GB RAM pada sistem saya dan beberapa aplikasi berjalan, jadi saya benar-benar terkejut grep sederhana kehabisan memori. ~/Documentssekitar 100GB dan berisi semua jenis file.

grep -RI mungkin tidak memiliki masalah ini, tetapi saya ingin mencari dalam file biner juga.

Nicolas Raoul
sumber

Jawaban:

46

Dua potensi masalah:

  • grep -R(kecuali untuk GNU dimodifikasi yang grepditemukan pada OS / X 10.8 dan di atas) mengikuti symlinks, jadi walaupun hanya ada 100GB file di ~/Documentsdalamnya, mungkin masih ada symlink /untuk misalnya dan Anda akan akhirnya memindai seluruh sistem file termasuk file seperti /dev/zero. Gunakan grep -rdengan GNU yang lebih baru grep, atau gunakan sintaks standar:

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

    (Namun perhatikan bahwa status keluar tidak akan mencerminkan fakta bahwa polanya cocok atau tidak).

  • grepmenemukan garis yang sesuai dengan pola. Untuk itu, ia harus memuat satu baris sekaligus dalam memori. GNU grepsebagai lawan dari banyak grepimplementasi lainnya tidak memiliki batasan pada ukuran baris yang dibaca dan mendukung pencarian dalam file biner. Jadi, jika Anda memiliki file dengan garis yang sangat besar (yaitu, dengan dua karakter baris baru yang sangat jauh), lebih besar dari memori yang tersedia, itu akan gagal.

    Itu biasanya akan terjadi dengan file yang jarang. Anda dapat mereproduksinya dengan:

    truncate -s200G some-file
    grep foo some-file
    

    Yang itu sulit untuk diselesaikan. Anda dapat melakukannya sebagai (masih dengan GNU grep):

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

    Itu mengubah urutan karakter NUL menjadi satu karakter baris baru sebelum memasukkan input ke grep. Itu akan mencakup untuk kasus-kasus di mana masalahnya adalah karena file jarang.

    Anda dapat mengoptimalkannya dengan melakukannya hanya untuk file besar:

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

    Jika file tidak jarang dan Anda memiliki versi GNU grepsebelumnya 2.6, Anda dapat menggunakan --mmapopsi. Garis-garis tersebut akan disimpan dalam memori sebagai lawan disalin di sana, yang berarti sistem selalu dapat mengklaim kembali memori dengan membuka halaman ke file. Opsi itu telah dihapus di GNU grep2.6

Stéphane Chazelas
sumber
Sebenarnya, GNU grep tidak peduli membaca dalam 1 baris, ia membaca sebagian besar file menjadi satu buffer. "Selain itu, GNU grep AVOIDS BREAKING INPUT KE LINES." sumber: lists.freebsd.org/pipermail/freebsd-current/2010-August/…
Godric Seer
4
@GodricSeer, mungkin masih membaca sebagian besar file ke buffer tunggal, tetapi jika belum menemukan string di sana dan belum menemukan karakter baris baru, saya bertaruh itu membuat buffer tunggal dalam memori. dan membaca buffer berikutnya, karena harus menampilkannya jika kecocokan ditemukan. Jadi, masalahnya masih sama. Dalam praktiknya, grep pada file jarang 200GB gagal dengan OOM.
Stéphane Chazelas
1
@ GodricSeer, baik tidak. Jika semua garis kecil, grepdapat membuang buffer yang telah diproses sejauh ini. Anda dapat grepoutput yestanpa batas tanpa menggunakan lebih dari beberapa kilobyte memori. Masalahnya adalah ukuran garis.
Stéphane Chazelas
3
--null-dataOpsi grep GNU juga dapat berguna di sini. Ini memaksa penggunaan NUL alih-alih baris baru sebagai terminator jalur input.
iruvar
1
@ 1_CR, titik yang baik, meskipun itu juga mengatur terminator jalur output ke NUL.
Stéphane Chazelas
5

Saya biasanya melakukannya

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

Saya mencoba banyak metode, dan menemukan ini menjadi yang tercepat. Perhatikan bahwa ini tidak menangani file dengan spasi nama file dengan sangat baik. Jika Anda tahu ini masalahnya dan memiliki versi grep GNU, Anda dapat menggunakan:

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

Jika tidak, Anda dapat menggunakan:

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

Yang akan execmenjadi grep untuk setiap file.

Kotte
sumber
Ini akan memecah file dengan spasi.
Chris Down
Hmm, itu benar.
Kotte
Anda dapat menyiasatinya denganfind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan
@ ChrisDown lebih merupakan solusi yang tidak dapat diproteksi daripada solusi yang rusak-portabel.
reto
@ChrisDown Sebagian besar persatuan besar telah mengadopsi find -print0dan xargs -0sekarang: ketiga BSD, MINIX 3, Solaris 11,…
Gilles 'SO- stop being evil'
4

Saya dapat memikirkan beberapa cara untuk mengatasi ini:

  • Alih-alih menangkap semua file sekaligus, lakukan satu file sekaligus. Contoh:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Jika Anda hanya perlu tahu file mana yang berisi kata-kata, lakukan grep -lsaja. Karena grep akan berhenti mencari setelah klik pertama, itu tidak harus terus membaca file besar

  • Jika Anda ingin teks yang sebenarnya juga, Anda dapat merangkai dua greps terpisah:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    
Jenny D
sumber
Contoh terakhir adalah sintaks yang tidak valid - Anda harus melakukan substitusi perintah (dan Anda seharusnya tidak melakukan itu, karena grepoutput menggunakan pembatas yang legal dalam nama file). Anda juga perlu mengutip $file.
Chris Down
Contoh terakhir menderita masalah nama file yang memiliki baris baru atau spasi putih di dalamnya, (itu akan menyebabkan forproses file sebagai dua argumen)
Drav Sloan
@ DropSloan Suntingan Anda, sementara perbaikan, masih memecah pada nama file hukum.
Chris Down
1
Ya saya meninggalkannya karena itu adalah bagian dari jawabannya, saya hanya mencoba memperbaikinya sehingga akan berjalan (untuk kasus-kasus di mana tidak ada spasi / baris baru dalam file).
Drav Sloan
Koreksi nya -> nya, saya minta maaf Jenny: /
Drav Sloan
1

Saya sedang mengambil disk 6TB untuk mencari data yang hilang, dan memori habis - teror. Ini juga bisa digunakan untuk file lain.

Solusi yang kami temukan adalah dengan membaca disk di chunks dengan menggunakan dd, dan grepping chunks. Ini adalah kode (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
sumber
1
Kecuali jika Anda membaca potongan yang tumpang tindih , Anda mungkin akan melewatkan pertandingan pada batas chunk. Tumpang tindih harus setidaknya sebesar string yang Anda harapkan cocok.
Kusalananda
Diperbarui untuk mencari tambahan 1MB di setiap potongan 100MB ... hack murah
Dagelf