grep: หน่วยความจำหมด


42

ฉันกำลังค้นหาง่าย ๆ :

grep -R Milledgeville ~/Documents

และหลังจากระยะเวลาหนึ่งข้อผิดพลาดนี้ปรากฏขึ้น:

grep: memory exhausted

ฉันจะหลีกเลี่ยงสิ่งนี้ได้อย่างไร

ฉันมี RAM 10GB ในระบบของฉันและมีแอปพลิเคชั่นบางตัวที่ทำงานอยู่ดังนั้นฉันจึงประหลาดใจจริงๆที่ grep ธรรมดาหมดหน่วยความจำ ~/Documentsประมาณ 100GB และมีไฟล์ทุกชนิด

grep -RI อาจไม่มีปัญหานี้ แต่ฉันต้องการค้นหาในไฟล์ไบนารีเช่นกัน

คำตอบ:


46

ปัญหาที่อาจเกิดขึ้นสองประการ:

  • grep -R(ยกเว้น GNU ที่แก้ไขแล้วที่grepพบใน OS / X 10.8 ขึ้นไป) ติดตาม symlinks ดังนั้นแม้ว่าจะมีไฟล์เพียง 100GB ใน~/Documentsนั้นก็อาจจะมี symlink เป็น/ตัวอย่างและคุณจะสิ้นสุดการสแกนระบบไฟล์ทั้งหมดรวมถึงไฟล์ /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ตัวเลือก บรรทัดจะถูก mmapped ในหน่วยความจำซึ่งตรงข้ามกับการคัดลอกที่นั่นซึ่งหมายความว่าระบบสามารถเรียกคืนหน่วยความจำได้เสมอโดยการเพจออกไปยังไฟล์ ตัวเลือกนั้นถูกลบใน GNU grep2.6


ที่จริงแล้ว GNU grep ไม่สนใจเรื่องการอ่านใน 1 บรรทัดมันอ่านไฟล์ส่วนใหญ่ในบัฟเฟอร์เดียว "ยิ่งไปกว่านั้น GNU grep AVOIDS ทำลายสายเข้าสู่" แหล่งที่มา: lists.freebsd.org/pipermail/freebsd-current/2010-August/ …
Godric Seer

4
@GodricSeer มันอาจยังอ่านไฟล์ส่วนใหญ่ในบัฟเฟอร์เดียว แต่ถ้ามันไม่พบสตริงในนั้นและไม่พบอักขระขึ้นบรรทัดใหม่การพนันของฉันคือมันเก็บบัฟเฟอร์เดียวในหน่วยความจำ และอ่านบัฟเฟอร์ถัดไปเนื่องจากจะต้องแสดงถ้าพบการจับคู่ ดังนั้นปัญหายังคงเหมือนเดิม ในทางปฏิบัติ grep ในไฟล์ sparse 200GB นั้นล้มเหลวด้วย OOM
Stéphane Chazelas

1
@GodricSeer ไม่เป็นไร หากบรรทัดมีขนาดเล็กgrepสามารถทิ้งบัฟเฟอร์ที่ประมวลผลแล้วได้ คุณสามารถgrepส่งออกyesอย่างไม่มีกำหนดโดยไม่ต้องใช้หน่วยความจำเกินสองสามกิโลไบต์ ปัญหาคือขนาดของเส้น
Stéphane Chazelas

3
--null-dataตัวเลือกgrep GNU อาจมีประโยชน์เช่นกันที่นี่ มันบังคับให้ใช้ NUL แทนการขึ้นบรรทัดใหม่เป็นตัวยุติบรรทัดอินพุต
iruvar

1
@ 1_CR จุดดีแม้ว่ามันจะตั้งค่า terminator ของบรรทัดเอาท์พุทเป็น NUL
Stéphane Chazelas

5

ฉันมักจะทำ

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

ฉันลองใช้วิธีการต่าง ๆ มากมายและพบว่านี่เป็นวิธีที่เร็วที่สุด โปรดทราบว่านี่ไม่ได้จัดการไฟล์ที่มีช่องว่างที่ชื่อไฟล์ได้เป็นอย่างดี หากคุณรู้ว่าเป็นกรณีนี้และมี grep รุ่น GNU คุณสามารถใช้:

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

ถ้าไม่ใช่คุณสามารถใช้:

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

ซึ่งจะexecgrep สำหรับทุกไฟล์


สิ่งนี้จะแตกไฟล์ที่มีช่องว่าง
Chris Down

อืมนั่นเป็นเรื่องจริง
Kotte

คุณสามารถไปfind -print0 | xargs -0 grep -ne 'expression'
ไหนมาไหนได้

@ChrisDown ค่อนข้างเป็นโซลูชันที่ไม่สามารถป้องกันได้มากกว่าโซลูชันแบบพกพาที่เสียหาย
reto

@ChrisDown Unices ที่สำคัญส่วนใหญ่ได้นำมาใช้find -print0และxargs -0ตอนนี้: ทั้งสาม BSD, MINIX 3, Solaris 11, …
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

4

ฉันสามารถนึกถึงวิธีที่จะหลีกเลี่ยงปัญหานี้:

  • แทนที่จะทำการ grepping ไฟล์ทั้งหมดในครั้งเดียวให้ทำทีละไฟล์ ตัวอย่าง:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • หากคุณจำเป็นต้องรู้ว่าไฟล์ใดมีคำอยู่ให้ทำgrep -lแทน เนื่องจาก grep จะหยุดค้นหาหลังจากการโจมตีครั้งแรกจึงไม่จำเป็นต้องอ่านไฟล์ขนาดใหญ่อีกต่อไป

  • หากคุณต้องการข้อความจริงเช่นกันคุณสามารถสตริง greps แยกกันสองตัวตาม:

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

ตัวอย่างสุดท้ายไม่ใช่ไวยากรณ์ที่ถูกต้อง - คุณต้องทำการทดแทนคำสั่ง (และคุณไม่ควรทำเช่นนั้นเนื่องจากgrepเอาต์พุตใช้ตัวคั่นที่ถูกกฎหมายในชื่อไฟล์) $fileนอกจากนี้คุณยังจะต้องอ้าง
Chris Down

ทนทุกข์ทรมานเช่นหลังกับปัญหาของชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่หรือช่องว่างในพวกเขา (มันจะก่อให้เกิดforการประมวลผลไฟล์เป็นสองอาร์กิวเมนต์)
Drav โลน

@DravSloan การแก้ไขของคุณในขณะที่การปรับปรุงยังคงแบ่งในชื่อไฟล์ทางกฎหมาย
Chris Down

1
ใช่ฉันทิ้งไว้เพราะมันเป็นส่วนหนึ่งของคำตอบของเธอฉันแค่พยายามปรับปรุงมันดังนั้นมันจะทำงาน (สำหรับกรณีที่ไม่มีช่องว่าง / บรรทัดใหม่ ฯลฯ ในไฟล์)
Drav Sloan

การแก้ไขของเขา -> เธอขอโทษของฉัน Jenny: /
Drav Sloan

1

ฉันกำลังพิมพ์ดิสก์ขนาด 6TB เพื่อค้นหาข้อมูลที่สูญหายและหน่วยความจำหมด สิ่งนี้ควรใช้ได้กับไฟล์อื่นด้วย

วิธีแก้ปัญหาที่เราคิดขึ้นมาก็คือการอ่านดิสก์เป็นชิ้นโดยใช้ dd และทำการ grepping chunks นี่คือรหัส (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

อัปเดตเพื่อค้นหา 1MB เพิ่มเติมในแต่ละก้อน 100MB ... แฮกราคาถูก
Dagelf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.