grep: bộ nhớ cạn kiệt


42

Tôi đã thực hiện một tìm kiếm rất đơn giản:

grep -R Milledgeville ~/Documents

Và sau một thời gian, lỗi này xuất hiện:

grep: memory exhausted

Làm thế nào tôi có thể tránh điều này?

Tôi có 10GB RAM trên hệ thống của mình và một vài ứng dụng đang chạy, vì vậy tôi thực sự ngạc nhiên khi một grep đơn giản hết bộ nhớ. ~/Documentslà khoảng 100 GB và chứa tất cả các loại tệp.

grep -RI có thể không có vấn đề này, nhưng tôi cũng muốn tìm kiếm trong các tệp nhị phân.

Câu trả lời:


46

Hai vấn đề tiềm ẩn:

  • grep -R(ngoại trừ GNU được sửa đổi grepđược tìm thấy trên OS / X 10.8 trở lên) tuân theo các liên kết tượng trưng, ​​vì vậy ngay cả khi chỉ có 100GB tệp ~/Documents, vẫn có thể có một liên kết tượng trưng /và cuối cùng bạn sẽ quét toàn bộ hệ thống tệp bao gồm các tệp như /dev/zero. Sử dụng grep -rvới GNU mới hơn grephoặc sử dụng cú pháp tiêu chuẩn:

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

    (tuy nhiên lưu ý rằng trạng thái thoát sẽ không phản ánh thực tế là mẫu đó có khớp hay không).

  • greptìm thấy các dòng phù hợp với mô hình. Vì vậy, nó phải tải một dòng tại một thời điểm trong bộ nhớ. GNU greptrái ngược với nhiều greptriển khai khác không có giới hạn về kích thước của các dòng nó đọc và hỗ trợ tìm kiếm trong các tệp nhị phân. Vì vậy, nếu bạn đã có một tệp có một dòng rất lớn (nghĩa là có hai ký tự dòng mới rất xa appart), lớn hơn bộ nhớ khả dụng, nó sẽ thất bại.

    Điều đó thường xảy ra với một tập tin thưa thớt. Bạn có thể sao chép nó bằng:

    truncate -s200G some-file
    grep foo some-file
    

    Đó là một khó khăn để làm việc xung quanh. Bạn có thể làm điều đó như (vẫn với GNU grep):

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

    Điều đó chuyển đổi các chuỗi ký tự NUL thành một ký tự dòng mới trước khi đưa đầu vào vào grep. Điều đó sẽ bao gồm cho các trường hợp vấn đề là do các tệp thưa thớt.

    Bạn có thể tối ưu hóa nó bằng cách chỉ thực hiện đối với các tệp lớn:

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

    Nếu các tệp không thưa thớt và bạn có phiên bản GNU greptrước đó 2.6, bạn có thể sử dụng --mmaptùy chọn này. Các dòng sẽ được ghép vào bộ nhớ trái ngược với sao chép ở đó, điều đó có nghĩa là hệ thống luôn có thể lấy lại bộ nhớ bằng cách phân trang các trang vào tệp. Tùy chọn đó đã bị xóa trong GNU grep2.6


Trên thực tế, GNU grep không quan tâm đến việc đọc trong 1 dòng, nó đọc một phần lớn tệp vào một bộ đệm. "Hơn nữa, GNU grep AVOIDS NÓI ĐẦU VÀO DÒNG." nguồn: lists.freebsd.org/pipermail/freebsd-current/2010-August/...
Godric Seer

4
@GodricSeer, nó vẫn có thể đọc một phần lớn tệp vào một bộ đệm, nhưng nếu nó không tìm thấy chuỗi trong đó và cũng không tìm thấy một ký tự dòng mới, tôi cá là nó sẽ giữ bộ đệm duy nhất đó trong bộ nhớ và đọc bộ đệm tiếp theo, vì nó sẽ phải hiển thị nó nếu tìm thấy kết quả khớp. Vì vậy, vấn đề vẫn như vậy. Trong thực tế, một grep trên tệp thưa thớt 200 GB không thành công với OOM.
Stéphane Chazelas

1
@GodricSeer, cũng không. Nếu các dòng đều nhỏ, grepcó thể loại bỏ bộ đệm mà nó đã xử lý cho đến nay. Bạn có thể grepxuất ra yesvô thời hạn mà không cần sử dụng nhiều hơn vài kilobyte bộ nhớ. Vấn đề kích thước của các dòng.
Stéphane Chazelas

3
--null-dataTùy chọn GNU grep cũng có thể hữu ích ở đây. Nó buộc sử dụng NUL thay vì dòng mới như một đầu cuối dòng đầu vào.
iruvar

1
@ 1_CR, điểm tốt, mặc dù điều đó cũng đặt đầu cuối dòng đầu ra thành NUL.
Stéphane Chazelas

5

tôi thường làm

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

Tôi đã thử một loạt các phương pháp, và thấy đây là cách nhanh nhất. Lưu ý rằng điều này không xử lý các tệp có khoảng trắng tên tệp rất tốt. Nếu bạn biết đây là trường hợp và có phiên bản GNU của grep, bạn có thể sử dụng:

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

Nếu không bạn có thể sử dụng:

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

Mà sẽ execmột grep cho mỗi tập tin.


Điều này sẽ phá vỡ trên các tập tin với không gian.
Chris Xuống

Hmm, đó là sự thật.
Kotte

Bạn có thể giải quyết vấn đề đó vớifind -print0 | xargs -0 grep -ne 'expression'
Drav Sloan

@ChrisDown thay vì một giải pháp không thể bảo vệ hơn là một giải pháp di động bị hỏng.
reto

@ChrisDown Hầu hết unices lớn đã áp dụng find -print0xargs -0bây giờ: cả ba BSD, MINIX 3, Solaris 11, ...
Gilles 'Somali dừng vốn là xấu'

4

Tôi có thể nghĩ ra một vài cách để khắc phục điều này:

  • Thay vì gre tất cả các tệp cùng một lúc, hãy thực hiện một tệp cùng một lúc. Thí dụ:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • Nếu bạn chỉ cần biết tập tin nào chứa các từ, grep -lthay vào đó hãy làm . Vì grep sẽ dừng tìm kiếm sau lần truy cập đầu tiên, nên sẽ không phải tiếp tục đọc bất kỳ tệp lớn nào

  • Nếu bạn cũng muốn văn bản thực tế, bạn có thể xâu chuỗi hai greps riêng biệt dọc theo:

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

Ví dụ cuối cùng không phải là cú pháp hợp lệ - bạn cần thực hiện thay thế lệnh (và bạn không nên làm điều đó, vì các grepkết quả đầu ra sử dụng một dấu phân cách hợp pháp trong tên tệp). Bạn cũng cần báo giá $file.
Chris Xuống

Ví dụ sau gặp vấn đề về tên tệp có dòng mới hoặc khoảng trắng trong chúng, (nó sẽ gây ra forxử lý tệp dưới dạng hai đối số)
Drav Sloan

@DravSloan Chỉnh sửa của bạn, trong khi cải tiến, vẫn vi phạm tên tệp hợp pháp.
Chris Xuống

1
Vâng, tôi đã để nó lại vì đó là một phần câu trả lời của cô ấy, tôi chỉ cố gắng cải thiện nó để nó chạy (đối với trường hợp không có khoảng trắng / dòng mới, v.v.) trong các tệp).
Drav Sloan

Sửa lỗi của anh ấy -> cô ấy, lời xin lỗi của tôi Jenny: /
Drav Sloan

1

Tôi đang gặt đĩa 6TB để tìm kiếm dữ liệu bị mất và bộ nhớ đã cạn kiệt. Điều này cũng sẽ làm việc cho các tập tin khác.

Giải pháp chúng tôi đã đưa ra là đọc đĩa theo từng khối bằng cách sử dụng dd và gunk các khối. Đây là mã (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
Trừ khi bạn đọc các đoạn chồng chéo , bạn có thể sẽ bỏ lỡ các trận đấu trên ranh giới khối. Sự trùng lặp ít nhất phải lớn bằng chuỗi mà bạn đang mong đợi khớp.
Kusalananda

Đã cập nhật để tìm kiếm thêm 1MB trong mỗi đoạn 100 MB ... hack giá rẻ
Dagelf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.