Büyük bir dosyanın ortasını okuyun


19

1 TB'lık bir dosyam var. Bayt 12345678901'den bayt 19876543212'ye okumak ve 100 MB RAM'e sahip bir makineye standart çıktıya koymak istiyorum.

Kolayca bunu yapan bir perl betiği yazabilirsiniz. sysread 700 MB / sn. (gayet iyi), ancak syswrite yalnızca 30 MB / sn. Daha verimli bir şey istiyorum, tercihen her Unix sistemini yükleyen ve 1 GB / s hızında teslim edebilen bir şey istiyorum.

İlk fikrim:

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

Ancak bu etkili değildir.

Düzenle:

Sistem yazısını nasıl yanlış ölçtüğüm hakkında hiçbir fikrim yok. Bu, 3,5 GB / s sunar:

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

ve yes | dd bs=1024k count=10 | wckabustan kaçınır .


ile komutunu bs=1M iflag=skip_bytes,count_bytes
ver

Yanıtlar:


21

Bu, küçük blok boyutu nedeniyle yavaştır. Yeni bir GNU dd( coreutils v8.16 + ) kullanarak, en basit yol skip_bytesve count_bytesseçeneklerini kullanmaktır :

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"

Güncelleme

fullblock@Gilles cevabına göre seçenek eklendi . İlk başta bunun ima edilebileceğini düşündüm count_bytes, ama durum böyle değil.

Bahsedilen sorunlar aşağıdaki potansiyel bir sorundur, eğer ddherhangi bir nedenle okuma / yazma aramaları kesintiye uğrarsa veriler kaybedilecektir. Çoğu durumda bu olası değildir (pipodan değil dosyadan okuduğumuz için oranlar biraz azalır).


A ve seçenekleri ddolmadan kullanmak daha zordur:skip_bytescount_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"
}

Farklı blok boyutları ile de deneme yapabilirsiniz, ancak kazançlar çok dramatik olmayacaktır. Bkz. - bs parametresinin dd'ye en uygun değeri belirlemenin bir yolu var mı?


@ Graze alışkanlık olmaz ikinci yöntemi başarısız bsbir faktör değilse skip?
Steven Penny

@StevenPenny, ne elde ettiğinden emin değilim, ama skipbir dizi blok, bayt değil. Çünkü Belki karıştı skip_bytesİlk örnek anlamında kullanılmaktadır skip olduğunu orada bayt?
Graeme

Sizin bs, 4,096yani 4,096baytları daha doğru bir şekilde atlayamayacağınız anlamına gelir
Steven Penny

1
@StevenPenny, bu yüzden bir blok hizalamasında başlamayan veya bitmeyen verileri kopyalamak için ddilk ve son kullanımda üç farklı çalışma vardır bs=1.
Graeme

6

bs=1ddher seferinde bir bayt okumayı ve yazmayı söyler . Her biri readve writeçağrı için bir ek yük vardır , bu da bunu yavaşlatır. İyi performans için daha büyük bir blok boyutu kullanın.

Tüm bir dosyayı kopyaladığınızda, en azından Linux altında, büyük bir blok boyutu belirtseniz bile, bunu buldum cpve catdaha hızlıdd .

Eğer boru can, dosyanın yalnızca bir bölümünü kopyalamak için tailiçine head. Bu, GNU coreutils veya head -cbelirtilen sayıda baytı kopyalaması gereken başka bir uygulamayı gerektirir ( tail -cPOSIX'te bulunur, ancak head -cyoktur). Linux üzerinde hızlı bir karşılaştırma, bunun ddmuhtemelen borudan dolayı daha yavaş olduğunu gösteriyor .

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

Sorun ddolduğunu güvenilir değildir: kısmi veri de kopyalayabilirsiniz . Bildiğim kadarıyla, ddnormal bir dosyaya okurken ve yazarken güvenlidir - bkz . Dd veri kopyalamak için ne zaman uygundur? (ya da, read () ve write () kısmi olduğunda) - ancak yalnızca bir sinyal tarafından kesilmediği sürece . GNU coreutils ile fullblockbayrağı kullanabilirsiniz , ancak bu taşınabilir değildir.

Başka bir sorun dd, çalışan bir blok sayısı bulmak zor olabilir, çünkü hem atlanan bayt sayısı hem de aktarılan bayt sayısı sayısının blok boyutunun katı olması gerekir. Birden fazla çağrıyı aşağıdakilerden ddbirini kullanarak yapabilirsiniz : biri ilk kısmi bloğu kopyalamak, diğeri hizalanmış blokların çoğunu kopyalamak ve diğeri son kısmi bloğu kopyalamak için kullanabilirsiniz - bkz. Graeme'in bir kabuk snippet'i için cevabı . Ancak, betiği çalıştırdığınızda, fullblockbayrağı kullanmadığınız sürece , ddtüm verileri kopyalayacak şekilde dua etmeniz gerektiğini unutmayın . ddbir kopya kısmi ise sıfırdan farklı bir durum döndürür, bu nedenle hatayı algılamak kolaydır, ancak onarmanın pratik bir yolu yoktur.

POSIX'in kabuk düzeyinde sunabileceği daha iyi bir şey yok. Benim tavsiyem küçük özel amaçlı C programı yazmak olacaktır (tam olarak uygulamak neyi bağlı arayabilirsin dd_done_rightya tail_headya mini-busybox).


Vay be, yes | dd bs=1024k count=10 | wcproblemi daha önce hiç bilmiyordum . Pis.
Ole Tange

4

İle dd:

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

Alternatif olarak losetup:

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

Ve sonra dd,, cat... döngü cihazı.


Bu çok Linux merkezli görünüyor. Ben de AIX, FreeBSD ve Solaris üzerinde çalışmak için aynı kodu gerekir.
Ole Tange

0

Bunu şu şekilde yapabilirsiniz:

   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

Gerçekten gerekli olan tek şey budur - daha fazlasını gerektirmez. İlk etapta hemen hemen düzenli dosya girişi üzerinde dd count=0 skip=1 bs=$block_size1olacak lseek(). Kaçırılan verilerin veya başka gerçek olmayan şeylerin söylendiği herhangi bir şans yoktur , doğrudan istediğiniz başlangıç ​​pozisyonunu arayabilirsiniz. Dosya tanıtıcısı kabuğa ait olduğundan ve dd'leri yalnızca miras aldığından, imleç konumunu etkiler ve böylece adım adım atabilirsiniz. Gerçekten çok basit - ve göreve daha uygun standart bir araç yok dd.

Bu genellikle ideal olan 64k'lık bir blok boyutu kullanır. Popüler inanışın aksine, daha büyük blok boyutları ddişi daha hızlı yapmaz . Öte yandan, küçük tamponlar da iyi değil. ddverileri sistem çağrılarında senkronize etmelidir, böylece verileri belleğe ve tekrar dışarı kopyalamayı beklemek zorunda kalmaz, aynı zamanda sistem çağrılarını beklemesine gerek kalmaz. Bu yüzden bir sonrakinin read()sonda beklemek zorunda kalmayacağı kadar zaman almasını istiyorsunuz , ancak gerekenden daha büyük boyutlarda arabelleğe aldığınız kadar değil.

Böylece ilk ddbaşlangıç ​​pozisyonuna atlar. Sıfır zaman alır . Bu noktada sevdiğiniz herhangi bir programı kendi stdin'ini okumak için çağırabilirsiniz ve doğrudan istediğiniz bayt ofsetinde okumaya başlayacaktır. Stdout'a sayım bloklarını ddokumak için başka birini arıyorum((interval / blocksize) -1)

Gerekli olan son şey , önceki bölüm işleminin modülünü (varsa) kopyalamaktır . Ve işte bu.

Bu arada, insanlar gerçekleri yüzlerinde kanıt olmadan ifade ettiklerinde inanmayın. Evet, ddkısa bir okuma yapmak mümkündür (ancak sağlıklı bir blok cihazdan - dolayısıyla adı okurken bu şeyler mümkün değildir) . Bu tür şeyler ancak ddbir blok cihazdan okunan bir akışı doğru şekilde arabelleğe almazsanız mümkündür . Örneğin:

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

Her iki durumda da tüm verileri ddkopyalar . İlk durumda, kopyalanan çıkış bloklarının bir kısmının "$ num" baytına eşit olması olasıdır (ancak olası olmasa da ) , yalnızca komutta özel olarak istendiğinde herhangi bir şeyi arabelleğe alması gerekir. hat. Bir temsil maksimum çünkü blok boyutu amaçlı bir gerçek zamanlı I / O olduğu.catddddbs=dd

İkinci örnekte, çıktı blok boyutunu açıkça ddbelirtiyorum ve tam yazma işlemleri gerçekleştirilinceye kadar tamponları okuyor. Bu, count=giriş bloklarına dayalı olanı etkilemez , ancak bunun için başka bir taneye ihtiyacınız vardır dd. Size başka şekilde verilen yanlış bilgiler göz ardı edilmelidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.