Lesen Sie die Mitte einer großen Datei

19

Ich habe eine 1 TB-Datei. Ich möchte von Byte 12345678901 zu Byte 19876543212 lesen und das auf Standardausgabe auf einer Maschine mit 100 MB RAM setzen.

Ich kann leicht ein Perl-Skript schreiben, das dies tut. Sysread liefert 700 MB / s (was in Ordnung ist), aber Syswrite liefert nur 30 MB / s. Ich möchte etwas effizienteres, vorzugsweise etwas, das auf jedem Unix-System installiert ist und das in der Größenordnung von 1 GB / s liefern kann.

Meine erste Idee ist:

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

Das ist aber nicht effizient.

Bearbeiten:

Ich habe keine Ahnung, wie ich Syswrite falsch gemessen habe. Dies 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.

Ole Tange
quelle
Ihr Befehl mitbs=1M iflag=skip_bytes,count_bytes
Frostschutz

Antworten:

21

Dies ist aufgrund der geringen Blockgröße langsam. Unter Verwendung einer aktuellen GNU dd( coreutils v8.16 + ) können Sie am einfachsten die Optionen skip_bytesund 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 hinzugefügt, wie in der @ Gilles-Antwort angegeben . Zuerst dachte ich, dass dies impliziert sein könnte count_bytes, aber das ist nicht der Fall.

Die im Folgenden genannten Probleme stellen möglicherweise ein Problem dar. Wenn ddLese- / Schreibaufrufe aus irgendeinem Grund unterbrochen werden, gehen Daten verloren. Dies ist in den meisten Fällen unwahrscheinlich (die Gewinnchancen sind etwas geringer, da wir aus einer Datei und nicht aus einer Pipe lesen).


Das Verwenden 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 Parameter bs für dd zu bestimmen?

Graeme
quelle
@Graeme wird die zweite Methode nicht scheitern, wenn bsnicht ein Faktor von skip?
Steven Penny
@StevenPenny, ich bin mir nicht sicher, worauf du hinaus willst, aber es skipgibt eine Reihe von Blöcken, keine Bytes. Vielleicht sind Sie verwirrt , da skip_bytesim ersten Beispiel Bedeutung verwendet wird skip ist es in Bytes?
Graeme
Sie bsheißt 4,096, was bedeutet , dass Sie nicht mehr genau überspringen , dass 4,096Bytes
Steven Penny
1
@StevenPenny, aus diesem Grund gibt es drei verschiedene Läufe, ddwobei der erste und der letzte verwendet bs=1werden, um die Daten zu kopieren, die bei einer Blockausrichtung nicht beginnen oder enden.
Graeme
6

bs=1sagt dd, dass jeweils ein Byte gelesen und geschrieben werden soll. Es gibt einen Overhead für jeden readund writeAufruf, was dies langsam macht. Verwenden Sie einen größeren Block, um eine angemessene Leistung zu erzielen.

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

So kopieren Sie nur einen Teil einer Datei, können Sie Rohr tailin head. Dies erfordert GNU Coreutils oder eine andere Implementierung, die head -ceine bestimmte Anzahl von Bytes kopieren muss ( tail -cin POSIX, aber head -cnicht in POSIX ). 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, dass es nicht zuverlässig ist: Es kann Teildaten kopieren . Soweit ich weiß, ddsicher ist , wenn sie auf eine normale Datei Lesen und Schreiben - siehe Wenn zum Kopieren von Daten dd geeignet ist? (oder, wenn read () und write () partiell sind) - aber nur solange es nicht durch ein Signal unterbrochen wird . Mit GNU coreutils können Sie das fullblockFlag verwenden, dies 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, um dd: einen zum Kopieren des ersten Teilblocks, einen zum Kopieren des Großteils der ausgerichteten Blöcke und einen zum Kopieren des letzten Teilblocks - siehe Graemes Antwort für ein Shell-Snippet. Vergessen Sie jedoch nicht, dass Sie beim Ausführen des Skripts fullblockbeten müssen, dddamit alle Daten kopiert werden , es sei denn, Sie verwenden die Flagge . ddGibt einen Status ungleich Null zurück, wenn eine Kopie partiell ist, sodass der Fehler leicht erkannt werden kann, es jedoch keine praktische Möglichkeit gibt, ihn zu reparieren.

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

Gilles 'SO - hör auf böse zu sein'
quelle
Wow, ich wusste das yes | dd bs=1024k count=10 | wcProblem noch nie . Böse.
Ole Tange
4

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.

Frostschutz
quelle
Dies scheint sehr Linux-zentriert zu sein. Ich benötige denselben Code auch für AIX, FreeBSD und Solaris.
Ole Tange
0

So kannst du das machen:

   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 notwendig ist - es erfordert nicht viel mehr. In erster Linie dd count=0 skip=1 bs=$block_size1wird lseek()über regelmäßige Dateieingabe praktisch augenblicklich. Es besteht keine Möglichkeit, dass Daten übersehen werden oder andere Unwahrheiten darüber gemeldet werden. Sie können einfach direkt zu Ihrer gewünschten Startposition suchen. Da der Dateideskriptor der Shell gehört und die Shell ihn ddlediglich erbt, wirkt er sich auf die Cursorposition aus, sodass Sie ihn schrittweise ausführen können. Es ist wirklich sehr einfach - und es gibt kein Standardwerkzeug, das besser für die Aufgabe geeignet ist als dd.

Dafür wird eine Blockgröße von 64 KB verwendet, was häufig ideal ist. Entgegen der landläufigen Meinung beschleunigen größere Blockgrößen die ddArbeit nicht . Andererseits sind winzige Puffer auch nicht gut. ddmuss seine Zeit in Systemaufrufen synchronisieren, damit es nicht warten muss, bis Daten in den Speicher kopiert und wieder ausgegeben werden, sondern auch, damit es nicht auf Systemaufrufe warten muss. Sie möchten also, dass es genügend Zeit in Anspruch read()nimmt , damit der nächste nicht auf den letzten warten muss, sondern nicht so viel, dass Sie in größeren Formaten als erforderlich puffern.

Der Erste ddspringt also zur Startposition. Das braucht keine Zeit. Sie können jedes andere Programm aufrufen, das Sie an diesem Punkt mochten, um seine Standard-ID zu lesen, und es würde direkt an Ihrem gewünschten Byte-Offset mit dem Lesen beginnen. Ich rufe einen anderen ddan, um ((interval / blocksize) -1)count blocks to stdout zu lesen .

Das letzte, was notwendig ist, ist das Herauskopieren des Moduls (falls vorhanden) der vorherigen Divisionsoperation. Und das ist das.

Glauben Sie es übrigens nicht, wenn die Leute Tatsachen ohne Beweise auf ihrem Gesicht ausdrücken. Ja, es ist möglich dd, einen kurzen Lesevorgang durchzuführen (obwohl dies beim Lesen von einem fehlerfreien Blockgerät nicht möglich ist - daher der Name) . Solche Dinge sind nur möglich, wenn Sie einen ddStream, der nicht von einem Block-Gerät gelesen wird, nicht korrekt puffern . Beispielsweise:

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

In beiden Fällen werden alle Daten ddkopiert . Im ersten Fall ist es möglich (wenn auch unwahrscheinlich ) , dass einige der Ausgangsblöcke , die Kopien aus Willen gleich „$ num“ Bit - Bytes , weil geskilled wird nur überhaupt etwas zu puffern , wenn der Puffer speziell auf ihre Kommando- angefordert wird Linie. stellt eine maximale Blockgröße dar, da der Zweck von Echtzeit-E / A ist.catddddbs=dd

Im zweiten Beispiel gebe ich explizit die Ausgabeblockgröße und die Pufferlesungen an, ddbis vollständige Schreibvorgänge durchgeführt werden können. Das wirkt sich nicht darauf aus, count=welche auf Eingabeblöcken basieren, aber dafür brauchen Sie einfach einen anderen dd. Jegliche Fehlinformationen, die Sie ansonsten erhalten, sollten ignoriert werden.

mikeserv
quelle