Baca tengah file besar


19

Saya memiliki file 1 TB. Saya ingin membaca dari byte 12345678901 ke byte 19876543212 dan meletakkannya pada output standar pada mesin dengan 100 MB RAM.

Saya dapat dengan mudah menulis skrip perl yang melakukan ini. sysread menghasilkan 700 MB / s (baik-baik saja), tetapi syswrite hanya menghasilkan 30 MB / s. Saya ingin sesuatu yang lebih efisien, lebih disukai sesuatu yang diinstal setiap sistem Unix dan dapat memberikan dalam urutan 1 GB / s.

Ide pertama saya adalah:

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

Tapi itu tidak efisien.

Edit:

Saya tidak tahu bagaimana saya mengukur syswrite salah. Ini menghasilkan 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

dan menghindari yes | dd bs=1024k count=10 | wcmimpi buruk.


perintah Anda denganbs=1M iflag=skip_bytes,count_bytes
frostschutz

Jawaban:


21

Ini lambat karena ukuran blok kecil. Menggunakan GNU terbaru dd( coreutils v8.16 + ), cara paling sederhana adalah dengan menggunakan opsi skip_bytesdan 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"

Memperbarui

fullblockopsi ditambahkan di atas sesuai jawaban @Gilles . Pada awalnya saya berpikir bahwa itu mungkin tersirat olehcount_bytes , tetapi ini tidak terjadi.

Masalah yang disebutkan adalah masalah potensial di bawah ini, jika ddpanggilan baca / tulis terputus karena alasan apa pun maka data akan hilang. Ini tidak mungkin dalam kebanyakan kasus (kemungkinan berkurang karena kita membaca dari file dan bukan pipa).


Menggunakan opsi ddtanpa skip_bytesdan count_byteslebih sulit:

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"
}

Anda juga bisa bereksperimen dengan ukuran blok yang berbeda, tetapi keuntungannya tidak akan terlalu dramatis. Lihat - Apakah ada cara untuk menentukan nilai optimal untuk parameter bs ke dd?


@ Greme tidak akan metode kedua gagal jika bsbukan merupakan faktor skip?
Steven Penny

@ Svenvenenny, tidak yakin apa yang Anda maksud, tetapi skipsejumlah blok, bukan byte. Mungkin Anda bingung karena skip_bytesdigunakan pada contoh pertama artinya skip ada dalam byte di sana?
Graeme

Ini bsadalah Anda 4,096, yang berarti Anda tidak dapat melewati 4,096byte dengan lebih akurat
Steven Penny

1
@ Svenvenenny, inilah mengapa ada tiga jalan berbeda dddengan menggunakan pertama dan terakhir bs=1untuk menyalin data yang tidak memulai atau selesai pada blok alignment.
Graeme

6

bs=1memberitahu dduntuk membaca dan menulis satu byte pada suatu waktu. Ada overhead untuk masing-masing readdan writepanggilan, yang membuat ini lambat. Gunakan ukuran blok yang lebih besar untuk kinerja yang layak.

Ketika Anda menyalin seluruh file, setidaknya di Linux, saya telah menemukan itu cpdan catlebih cepat daripadadd , bahkan jika Anda menentukan ukuran blok yang besar.

Untuk menyalin hanya sebagian file, Anda dapat tailmemasukkan ke head. Ini membutuhkan GNU coreutils atau implementasi lain yang harus head -cmenyalin sejumlah byte tertentu ( tail -cada dalam POSIX tetapi head -ctidak). Patokan cepat di Linux menunjukkan ini lebih lambat daripada dd, mungkin karena pipa.

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

Masalahnya ddadalah bahwa itu tidak dapat diandalkan: ia dapat menyalin sebagian data . Sejauh yang saya tahu, ddapakah aman saat membaca dan menulis ke file biasa - lihat Kapan dd cocok untuk menyalin data? (atau, ketika dibaca () dan tulis () parsial) - tetapi hanya selama itu tidak terganggu oleh sinyal . Dengan GNU coreutils, Anda dapat menggunakan fullblockflag, tetapi ini tidak portabel.

Masalah lainnya ddadalah sulit untuk menemukan jumlah blok yang berfungsi, karena jumlah byte yang dilewati dan jumlah byte yang ditransfer harus berupa kelipatan dari ukuran blok. Anda dapat menggunakan beberapa panggilan untuk dd: satu untuk menyalin blok parsial pertama, satu untuk menyalin sebagian besar blok yang selaras dan satu untuk menyalin blok parsial terakhir - lihat jawaban Graeme untuk cuplikan shell. Tetapi jangan lupa bahwa Anda ketika Anda menjalankan skrip, kecuali jika Anda menggunakan fullblockbendera, Anda harus berdoa agar itu ddakan menyalin semua data.ddmengembalikan status bukan nol jika salinannya parsial, sehingga mudah untuk mendeteksi kesalahan, tetapi tidak ada cara praktis untuk memperbaikinya.

POSIX tidak ada yang lebih baik untuk ditawarkan di level shell. Saran saya adalah menulis sebuah program C tujuan-kecil khusus (tergantung pada apa yang Anda terapkan, Anda dapat menyebutnya dd_done_rightatau tail_headatau mini-busybox).


Wow, saya tidak pernah tahu yes | dd bs=1024k count=10 | wcmasalahnya sebelumnya. Menjijikan.
Ole Tange

4

Dengan dd:

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

Atau dengan losetup:

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

Dan kemudian dd,, cat... perangkat loop.


Ini tampaknya sangat sentris Linux. Saya memerlukan kode yang sama untuk bekerja pada AIX, FreeBSD, dan Solaris juga.
Ole Tange

0

Ini adalah bagaimana Anda melakukannya, Anda bisa melakukan ini:

   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

Itu semua yang benar-benar diperlukan - tidak memerlukan lebih banyak Di tempat pertama dd count=0 skip=1 bs=$block_size1akan lseek()lebih dari input file biasa praktis instan. Tidak ada peluang data yang terlewat atau apa pun yang tidak benar lainnya diberitahu tentang hal itu, Anda bisa mencari langsung ke posisi awal yang Anda inginkan. Karena deskriptor file dimiliki oleh shell dan dditu hanya mewarisinya, mereka akan mempengaruhi posisi kursornya dan jadi Anda bisa mengambilnya dalam langkah-langkah. Ini sangat sederhana - dan tidak ada alat standar yang lebih cocok untuk tugas itu dd.

Itu menggunakan 64k blocksize yang sering ideal. Berlawanan dengan kepercayaan populer, pemblokiran yang lebih besar tidak membuat ddpekerjaan lebih cepat. Di sisi lain, buffer kecil juga tidak bagus. ddperlu menyinkronkan waktunya dalam panggilan sistem sehingga tidak perlu menunggu menyalin data ke dalam memori dan keluar lagi, tetapi juga agar tidak perlu menunggu panggilan sistem. Jadi, Anda ingin mengambil waktu yang cukup berikutnyaread() tidak harus menunggu pada yang terakhir, tetapi tidak terlalu banyak sehingga Anda buffering dalam ukuran yang lebih besar daripada yang diperlukan.

Jadi yang pertama ddlompat ke posisi awal. Itu membutuhkan nol waktu. Anda dapat memanggil program lain yang Anda suka pada saat itu untuk membaca stdin-nya dan akan mulai membaca langsung di byte byte yang Anda inginkan. Saya memanggil orang lain dduntuk membaca((interval / blocksize) -1) blok hitungan ke stdout.

Hal terakhir yang diperlukan adalah menyalin modulus (jika ada) dari operasi divisi sebelumnya. Dan itu saja.

Omong-omong, jangan percaya ketika orang menyatakan fakta di wajah mereka tanpa bukti. Ya, dimungkinkan untuk ddmelakukan pembacaan singkat (meskipun hal-hal seperti itu tidak mungkin ketika membaca dari perangkat blok yang sehat - demikian namanya) . Hal-hal seperti itu hanya mungkin jika Anda tidak benar buffer ddaliran yang dibaca dari selain perangkat blok. Sebagai contoh:

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

Dalam kedua kasus ddmenyalin semua data. Dalam kasus pertama adalah mungkin (meskipun tidak mungkin dengan cat) beberapa blok keluaran yangdd menyalin akan menggigit sama dengan "$ num" byte karena ddditentukan hanya untuk buffer apa saja ketika buffer secara khusus diminta pada perintahnya- baris. bs=merupakan maksimum blok-ukuran karena tujuan dari ddadalah real-time i / o.

Dalam contoh kedua saya secara eksplisit menentukan keluaran blocksize dan dd buffer membaca hingga penulisan lengkap dapat dilakukan. Itu tidak mempengaruhi count=yang didasarkan pada blok input, tetapi untuk itu Anda hanya perlu yang lain dd. Setiap informasi yang salah yang diberikan kepada Anda sebaliknya harus diabaikan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.