อ่านตรงกลางของไฟล์ขนาดใหญ่


19

ฉันมีไฟล์ 1 TB ฉันต้องการอ่านจากไบต์ 12345678901 ถึงไบต์ 19876543212 และวางบนเอาต์พุตมาตรฐานบนเครื่องที่มี RAM ขนาด 100 MB

ฉันสามารถเขียนสคริปต์ Perl ที่ทำสิ่งนี้ได้อย่างง่ายดาย sysread ส่ง 700 MB / s (ซึ่งใช้ได้) แต่ syswrite ให้ 30 MB / s เท่านั้น ฉันต้องการบางสิ่งที่มีประสิทธิภาพมากกว่าโดยเฉพาะอย่างยิ่งสิ่งที่ติดตั้งทุกระบบ Unix และสามารถส่งได้ในระดับ 1 GB / s

ความคิดแรกของฉันคือ:

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

แต่นั่นไม่ได้มีประสิทธิภาพ

แก้ไข:

ฉันไม่รู้ว่าฉันวัดซิงไซต์ผิดอย่างไร สิ่งนี้ให้ 3.5 GB / s:

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

และหลีกเลี่ยงyes | dd bs=1024k count=10 | wcฝันร้าย


คำสั่งของคุณกับbs=1M iflag=skip_bytes,count_bytes
frostschutz

คำตอบ:


21

สิ่งนี้ช้าเพราะขนาดบล็อกเล็ก การใช้ GNU ล่าสุดdd( coreutils v8.16 + ) วิธีที่ง่ายที่สุดคือใช้skip_bytesและcount_bytesตัวเลือก:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

ปรับปรุง

fullblockตัวเลือกเพิ่มดังกล่าวข้างต้นเป็นไปตามคำตอบ @Gilles ตอนแรกฉันคิดว่ามันอาจจะบอกเป็นนัยcount_bytesแต่นี่ไม่ใช่กรณี

ปัญหาที่กล่าวถึงเป็นปัญหาที่อาจเกิดขึ้นด้านล่างหากการddโทรแบบอ่าน / เขียนถูกขัดจังหวะด้วยสาเหตุใดก็ตามข้อมูลจะสูญหาย นี่ไม่ใช่กรณีส่วนใหญ่ (โอกาสจะลดลงบ้างเนื่องจากเราอ่านจากไฟล์และไม่ใช่ไปป์)


การใช้แบบddไม่มีskip_bytesและcount_bytesตัวเลือกนั้นยากกว่า:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

คุณสามารถทดสอบด้วยขนาดบล็อกที่แตกต่างกันได้ แต่กำไรจะไม่น่าทึ่งมาก ดู - มีวิธีการกำหนดค่าที่เหมาะสมที่สุดสำหรับพารามิเตอร์ bs เป็น dd หรือไม่


@ Graeme จะไม่ใช้วิธีที่สองล้มเหลวหากbsไม่ใช่ปัจจัยของskip?
Steven Penny

@StevenPenny ไม่แน่ใจว่าคุณได้รับอะไร แต่skipเป็นบล็อกจำนวนมากไม่ใช่ไบต์ บางทีคุณอาจสับสนตั้งแต่skip_bytesใช้ในตัวอย่างแรกความหมายskip มีหน่วยเป็นไบต์?
แกรม

คุณbsมี4,096ซึ่งหมายความว่าคุณไม่สามารถข้ามได้แม่นยำมากขึ้นว่า4,096ไบต์
สตีเว่นขี้

1
@StevenPenny นี่คือเหตุผลว่าทำไมการรันddครั้งแรกและครั้งสุดท้ายจึงมีการดำเนินการที่แตกต่างกันสามครั้งbs=1เพื่อคัดลอกข้อมูลที่ไม่เริ่มต้นหรือสิ้นสุดในการจัดแนวบล็อก
แกรม

6

bs=1บอกddให้อ่านและเขียนทีละหนึ่งไบต์ มีค่าใช้จ่ายสำหรับแต่ละคนreadและการwriteโทรซึ่งทำให้ช้านี้ ใช้ขนาดบล็อกที่ใหญ่ขึ้นเพื่อประสิทธิภาพที่ดี

เมื่อคุณคัดลอกไฟล์ทั้งหมดอย่างน้อยภายใต้ Linux, ฉันพบว่าcpและcatจะเร็วกว่าddแม้ว่าคุณจะระบุขนาดบล็อกขนาดใหญ่

หากต้องการคัดลอกไฟล์เพียงบางส่วนคุณสามารถไพพ์tailลงไปheadได้ สิ่งนี้ต้องใช้ coreutils ของ GNU หรือการนำไปใช้งานอื่นที่head -cต้องคัดลอกจำนวนไบต์ที่ระบุ ( tail -cอยู่ใน POSIX แต่head -cไม่ใช่) เกณฑ์มาตรฐานอย่างรวดเร็วบน Linux แสดงให้เห็นว่าสิ่งนี้ช้ากว่าddอาจเป็นเพราะไปป์

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

ปัญหากับddคือว่ามันไม่น่าเชื่อถือ: มันสามารถคัดลอกข้อมูลบางส่วน เท่าที่ฉันรู้ddจะปลอดภัยเมื่ออ่านและเขียนไปยังไฟล์ปกติ - ดูเมื่อไหร่เหมาะสำหรับการคัดลอกข้อมูล (หรือเมื่อมีการอ่าน () และเขียน () บางส่วน) - แต่เพียงตราบเท่าที่มันจะไม่ถูกขัดจังหวะโดยสัญญาณ ด้วย GNU coreutils คุณสามารถใช้การfullblockตั้งค่าสถานะได้ แต่นี่ไม่ใช่แบบพกพา

ปัญหาอีกประการหนึ่งddคือการหาจำนวนบล็อกที่ใช้งานได้ยากเนื่องจากทั้งจำนวนไบต์ที่ถูกข้ามและจำนวนไบต์ที่ถ่ายโอนต้องมีขนาดบล็อกหลายเท่า คุณสามารถใช้การเรียกหลายครั้งเพื่อdd: หนึ่งครั้งเพื่อคัดลอกบล็อกบางส่วนแรก, หนึ่งสำเนาเพื่อคัดลอกบล็อกที่จัดเรียงจำนวนมากและอีกหนึ่งรายการเพื่อคัดลอกบล็อกบางส่วนล่าสุด - ดูคำตอบของ Graemeสำหรับตัวอย่างเชลล์ แต่อย่าลืมว่าเมื่อคุณเรียกใช้สคริปต์ยกเว้นว่าคุณกำลังใช้fullblockแฟล็กคุณจะต้องอธิษฐานเพื่อddจะคัดลอกข้อมูลทั้งหมด ddส่งคืนสถานะที่ไม่ใช่ศูนย์หากสำเนาเป็นบางส่วนดังนั้นจึงง่ายต่อการตรวจสอบข้อผิดพลาด แต่ไม่มีวิธีการปฏิบัติในการซ่อมแซม

POSIX ไม่มีอะไรที่ดีกว่าที่จะนำเสนอในระดับเชลล์ คำแนะนำของฉันคือการเขียนโปรแกรม C วัตถุประสงค์พิเศษขนาดเล็ก (ขึ้นอยู่กับสิ่งที่คุณใช้คุณสามารถเรียกมันdd_done_rightหรือtail_headหรือmini-busybox)


ว้าวฉันไม่เคยรู้yes | dd bs=1024k count=10 | wcปัญหามาก่อน น่ารังเกียจ
Ole Tange

4

ด้วยdd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

อีกทางเลือกด้วยlosetup:

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

จากนั้นdd, cat... อุปกรณ์ลูป


ดูเหมือนว่าศูนย์กลางลินุกซ์มาก ฉันต้องการรหัสเดียวกันเพื่อทำงานกับ AIX, FreeBSD และ Solaris ด้วย
Ole Tange

0

นี่คือวิธีที่คุณสามารถทำได้:

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

นั่นคือทั้งหมดที่จำเป็นจริงๆ - มันไม่ต้องการอะไรมากมาย ในสถานที่แรกdd count=0 skip=1 bs=$block_size1จะlseek()ผ่านการป้อนไฟล์ปกติในทางปฏิบัติทันที ไม่มีโอกาสที่จะพลาดข้อมูลใด ๆ หรือมีการบอกถึงความจริงใด ๆ เกี่ยวกับเรื่องนี้คุณสามารถค้นหาตำแหน่งเริ่มต้นที่คุณต้องการได้โดยตรง เนื่องจาก file descriptor เป็นเจ้าของโดยเชลล์และddเป็นเพียงการสืบทอดมันจะส่งผลต่อตำแหน่งเคอร์เซอร์และคุณสามารถทำตามขั้นตอน จริงๆมันเป็นเรื่องง่ายมาก - ddและไม่มีเครื่องมือมาตรฐานดีเหมาะกับงานมากกว่า

ที่ใช้ขนาดบล็อก 64k ซึ่งมักจะเหมาะ ขัดกับความเชื่อที่ได้รับความนิยมบล็อคขนาดใหญ่ไม่ddทำงานเร็ว ในทางกลับกันบัฟเฟอร์เล็ก ๆ ก็ไม่ดีเช่นกัน ddจำเป็นต้องซิงโครไนซ์เวลาในการเรียกใช้ระบบเพื่อให้ไม่จำเป็นต้องรอในการคัดลอกข้อมูลลงในหน่วยความจำและออกอีกครั้ง แต่ยังไม่จำเป็นต้องรอในการเรียกระบบ ดังนั้นคุณต้องการให้มีเวลามากพอที่สิ่งต่อไปread()ไม่จำเป็นต้องรอในขั้นสุดท้าย แต่ไม่มากจนคุณต้องบัฟเฟอร์ในขนาดที่ใหญ่กว่าที่จำเป็น

ดังนั้นddข้ามแรกไปยังตำแหน่งเริ่มต้น ที่ใช้เป็นศูนย์เวลา คุณสามารถเรียกโปรแกรมอื่น ๆ ที่คุณชอบ ณ จุดนั้นเพื่ออ่าน stdin และมันจะเริ่มอ่านโดยตรงที่ไบต์ที่คุณต้องการ ฉันโทรddไปอีกอันเพื่ออ่าน((interval / blocksize) -1)count blocks ถึง stdout

สิ่งสุดท้ายที่จำเป็นคือการคัดลอกโมดูลัส(ถ้ามี)ของการแบ่งส่วนก่อนหน้านี้ และนั่นคือสิ่งที่

อย่าเชื่อเมื่อผู้คนระบุข้อเท็จจริงบนใบหน้าของพวกเขาโดยไม่มีหลักฐาน ใช่มันเป็นไปได้สำหรับddการทำอ่านสั้น(แม้ว่าสิ่งนั้นเป็นไปไม่ได้เมื่ออ่านจากอุปกรณ์ป้องกันเพื่อสุขภาพ - จึงชื่อ) สิ่งต่าง ๆ เหล่านี้เป็นไปได้เฉพาะถ้าคุณทำไม่ถูกต้องบัฟเฟอร์ddกระแสที่อ่านจากอื่นนอกเหนือจากอุปกรณ์บล็อก ตัวอย่างเช่น:

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

ในทั้งสองกรณีddสำเนาทั้งหมดของข้อมูล ในกรณีแรกเป็นไปได้(แต่ไม่น่าcatเป็นไปได้ด้วย)ว่าบล็อกเอาต์พุตบางตัวที่ddคัดลอกออกจะบิตเท่ากับ "$ num" ไบต์เพราะddมันเป็นสเป็คเพียงเพื่อบัฟเฟอร์อะไรเลยเมื่อบัฟเฟอร์ถูกร้องขอโดยเฉพาะในคำสั่งของมัน - ไลน์. bs=แสดงถึงขนาดบล็อกสูงสุดเนื่องจากจุดประสงค์ของการddเป็นแบบ real-time i / o

ในตัวอย่างที่สองฉันระบุการบล็อกเอาท์พุทและddบัฟเฟอร์อย่างชัดเจนจนกระทั่งสามารถเขียนได้อย่างสมบูรณ์ แต่นั่นไม่ได้ส่งผลกระทบต่อcount=ที่อยู่บนพื้นฐานของการป้อนข้อมูลบล็อก ddแต่การที่คุณเพียงแค่ต้องอีก ข้อมูลที่ผิดใด ๆ ที่ให้ไว้กับคุณเป็นอย่างอื่นควรได้รับการเพิกเฉย

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