Lesen Sie die Mitte einer großen Datei

Lesen Sie die Mitte einer großen Datei

Ich habe eine 1 TB große Datei. Ich möchte von Byte 12345678901 bis Byte 19876543212 lesen und das auf der Standardausgabe auf einem Rechner mit 100 MB RAM ausgeben.

Ich kann problemlos ein Perl-Skript schreiben, das dies tut. sysread liefert 700 MB/s (was in Ordnung ist), aber syswrite liefert nur 30 MB/s. Ich hätte gerne etwas Effizienteres, vorzugsweise etwas, das auf jedem Unix-System installiert ist und etwa 1 GB/s liefern kann.

Meine erste Idee ist:

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

Aber das ist nicht effizient.

Bearbeiten:

Ich habe keine Ahnung, wie ich das Syswrite falsch gemessen habe. Das hier liefert 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

und vermeidet den yes | dd bs=1024k count=10 | wcAlbtraum.

Antwort1

Dies ist aufgrund der kleinen Blockgröße langsam. Mit einem aktuellen GNU dd(coreutils v8.16 +) ist es am einfachsten, die Optionen skip_bytesund zu verwenden 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"

Aktualisieren

fullblockOption oben hinzugefügt gemäß@Gilles Antwort. Zuerst dachte ich, dass es mit impliziert sein könnte count_bytes, aber das ist nicht der Fall.

Die unten genannten Probleme sind ein potenzielles Problem. Wenn ddLese-/Schreibaufrufe aus irgendeinem Grund unterbrochen werden, gehen Daten verloren. Dies ist in den meisten Fällen unwahrscheinlich (die Wahrscheinlichkeit ist etwas geringer, da wir aus einer Datei und nicht aus einer Pipe lesen).


Die Verwendung von a ddohne die Optionen skip_bytesund count_bytesist schwieriger:

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

Sie können auch mit verschiedenen Blockgrößen experimentieren, aber die Gewinne werden nicht sehr dramatisch sein. Siehe -Gibt es eine Möglichkeit, den optimalen Wert für den bs-Parameter für dd zu bestimmen?

Antwort2

bs=1weist an, ddjeweils ein Byte zu lesen und zu schreiben. Für jeden Aufruf entsteht ein Overhead read, writeder dies verlangsamt. Verwenden Sie für eine angemessene Leistung eine größere Blockgröße.

Wenn Sie eine ganze Datei kopieren, zumindest unter Linux, habe ich festgestellt, dasscpund catsind schneller alsdd, auch wenn Sie eine große Blockgröße angeben.

Um nur einen Teil einer Datei zu kopieren, können Sie eine Pipe tailin verwenden head. Dies erfordert GNU coreutils oder eine andere Implementierung, die head -ceine bestimmte Anzahl von Bytes kopieren muss ( tail -cist in POSIX, aber head -cnicht). Ein schneller Benchmark unter Linux zeigt, dass dies langsamer ist als dd, vermutlich wegen der Pipe.

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

Das Problem dabei ddist, dasses ist nicht zuverlässig: es kann Teildaten kopieren. Soweit ich weiß, ddist es sicher beim Lesen und Schreiben in eine normale Datei — sieheWann ist dd zum Kopieren von Daten geeignet? (oder wann sind read() und write() partiell)- Abernur solange es nicht durch ein Signal unterbrochen wird. Mit GNU Coreutils können Sie das fullblockFlag verwenden, es ist jedoch nicht portierbar.

Ein weiteres Problem ddist, dass es schwierig sein kann, eine funktionierende Blockanzahl zu finden, da sowohl die Anzahl der übersprungenen Bytes als auch die Anzahl der übertragenen Bytes ein Vielfaches der Blockgröße sein müssen. Sie können mehrere Aufrufe verwenden dd: einen zum Kopieren des ersten Teilblocks, einen zum Kopieren der Masse der ausgerichteten Blöcke und einen zum Kopieren des letzten Teilblocks – sieheGraemes Antwortfür ein Shell-Snippet. Aber vergessen Sie nicht, dass Sie beim Ausführen des Skripts, sofern Sie das Flag nicht verwenden fullblock, beten müssen, dass ddalle Daten kopiert werden. ddgibt einen Status ungleich Null zurück, wenn eine Kopie teilweise ist, sodass der Fehler leicht zu erkennen ist, es aber keine praktische Möglichkeit gibt, ihn zu beheben.

POSIX hat auf Shell-Ebene nichts Besseres zu bieten. Mein Rat wäre, ein kleines Spezialprogramm in C zu schreiben (je nachdem, was Sie genau implementieren, können Sie es dd_done_rightoder tail_headoder nennen mini-busybox).

Antwort3

Mit dd:

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

Alternativ mit losetup:

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

Und dann dd, cat, … das Loop-Gerät.

Antwort4

So können Sie vorgehen:

   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

Das ist alles, was wirklich nötig ist - viel mehr ist nicht erforderlich. Zunächst dd count=0 skip=1 bs=$block_size1wird lseek()die normale Dateieingabe praktisch sofort durchgeführt. Es besteht keine Chance,verpasste Datenoder was auch immer für Unwahrheiten darüber erzählt werden, Sie können einfach direkt zu Ihrer gewünschten Startposition suchen. Da der Dateideskriptor Eigentum der Shell ist und die dd's ihn lediglich erben, beeinflussen sie die Cursorposition und Sie können es also einfach schrittweise angehen. Es ist wirklich sehr einfach – und es gibt kein Standardtool, das für diese Aufgabe besser geeignet ist als dd.

Dabei wird eine Blockgröße von 64 KB verwendet, die oft ideal ist. Entgegen der landläufigen Meinung beschleunigen größere Blockgrößen dddie Arbeit nicht. Andererseits sind winzige Puffer auch nicht gut. ddmuss seine Zeit bei Systemaufrufen synchronisieren, damit es nicht warten muss, wenn Daten in den Speicher kopiert und wieder herauskopiert werden, aber auch, damit es nicht auf Systemaufrufe warten muss. Sie möchten also, dass es genug Zeit braucht, damit der nächste read()nicht auf den letzten warten muss, aber nicht so viel, dass Sie in größeren Größen puffern als nötig.

Der erste ddspringt also zur Startposition. Das dauertnullZeit. Sie könnten an dieser Stelle jedes beliebige andere Programm aufrufen, um dessen Standardeingabe zu lesen, und es würde direkt an der gewünschten Byte-Position mit dem Lesen beginnen. Ich rufe ein anderes auf, ddum zu lesen((interval / blocksize) -1)Blöcke bis zur Standardausgabe zählen.

Als letztes muss noch der Modul ausgedruckt werden(wenn überhaupt)der vorherigen Divisionsoperation. Und das war's.

Glauben Sie es übrigens nicht, wenn Leute Tatsachen offen darlegen, ohne Beweise zu haben. Ja, es ist möglich, ddeine kurze Lektüre zu machen(obwohl solche Dinge beim Lesen von einem fehlerfreien Blockgerät nicht möglich sind – daher der Name)dd. Solche Dinge sind nur möglich, wenn Sie einen Stream, der von einem anderen als einem Blockgerät gelesen wird, nicht korrekt puffern . Beispiel:

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

In beiden Fällen ddKopienalleder Daten. Im ersten Fall ist es möglich(obwohl unwahrscheinlich mit cat)dass einige der Ausgabeblöcke, die ddkopiert werden, bitgleich "$num" Bytes sind, weil ddangegeben istnurüberhaupt etwas zu puffern, wenn der Puffer ausdrücklich in der Befehlszeile angefordert wird. bs=stellt einemaximalBlockgröße, da dieZweckdavon ddist Echtzeit-E/A.

Im zweiten Beispiel gebe ich die Ausgabeblockgröße explizit an und ddpuffere Lesevorgänge, bis vollständige Schreibvorgänge durchgeführt werden können. Das hat keinen Einfluss darauf, count=was auf den Eingabeblöcken basiert, aber dafür benötigen Sie einfach ein weiteres dd. Alle Fehlinformationen, die Ihnen sonst gegeben werden, sollten ignoriert werden.

verwandte Informationen