Dnes jsem musel odstranit prvních 1131 bajtů ze 800 MB smíšeného textového/binárního souboru, filtrovaného výpisu Subversion, který hackuji do nového úložiště. Jaký je nejlepší způsob, jak toho dosáhnout?
Nejprve jsem to zkusil
dd bs=1 skip=1131 if=filtered.dump of=trimmed.dump
ale po přeskočení to zkopíruje zbytek souboru bajt najednou, tj. velmi pomalu. Nakonec jsem vypracoval, že jsem potřeboval 405 bytů, abych to zaokrouhlil na tři bloky po 512, které bych mohl přeskočit
dd if=/dev/zero of=405zeros bs=1 count=405
cat 405zeros filtered.dump | dd bs=512 skip=3 of=trimmed.dump
která skončila poměrně rychle, ale muselo existovat jednodušší/lepší způsob? Je tu další nástroj, na který jsem zapomněl? Dík!
Můžete přepínat možnosti bs a přeskočit:
dd bs=1131 skip=1 if=filtered.dump of=trimmed.dump
Tímto způsobem může operace těžit z většího bloku.
V opačném případě můžete zkusit s ocasem (ačkoli není bezpečné používat jej s binárními soubory):
tail -c +1132 filtered.dump >trimmed.dump
Nakonec můžete použít 3 dd instance k napsání něčeho takového:
dd if=filtered.dump bs=512k | { dd bs=1131 count=1 of=/dev/null; dd bs=512k of=trimmed.dump; }
kde první dd vytiskne svůj standardní výstup filtrovaný.dump; druhý právě přečte 1131 bytů a zahodí je; potom poslední přečte ze svého standardního vstupu zbývající bajty filtrovaného.dumpu a zapíše je do oříznutého.dumpu.
Nejste si jisti, kdy skip_bytes
byl přidán, ale přeskočit prvních 11 bajtů máte:
# echo {123456789}-abcdefgh- |
dd bs=4096 skip=11 iflag=skip_bytes
-abcdefgh-
0+1 records in
0+1 records out
11 bytes (11 B) copied, 6.963e-05 s, 158 kB/s
Kde iflag=skip_bytes
řekne dd, aby interpretoval hodnotu volby skip
jako bajty namísto bloků, což je jednoduché.
Můžete použít subshell a dva dd
hovory takto:
$ ( dd bs=1131 count=1 of=dev_null && dd bs=4K of=out.mp3 ) < 100827_MR029_LobbyControl.mp3
1+0 records in
1+0 records out
1131 bytes (1.1 kB) copied, 7.9691e-05 s, 14.2 MB/s
22433+1 records in
22433+1 records out
91886130 bytes (92 MB) copied, 0.329823 s, 279 MB/s
$ ls -l *
-rw------- 1 max users 91887261 2011-02-03 22:59 100827_MR029_LobbyControl.mp3
-rw-r--r-- 1 max users 1131 2011-02-03 23:04 dev_null
-rw-r--r-- 1 max users 91886130 2011-02-03 23:04 out.mp3
$ cat dev_null out.mp3 > orig
$ cmp 100827_MR029_LobbyControl.mp3 orig
Pokud to souborový systém a linuxové jádro podporují, můžete zkusit fallocate
, pokud chcete provést změny na místě: v nejlepším případě neexistují žádná data IO vůbec:
$ fallocate <magic> -o 0 -l 1131 inplace.dump
kde <magic>
záleží na systému souborů, verzi systému Linux a typu souboru ( FALLOC_FL_COLLAPSE_RANGE
nebo FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE
lze použít interně ).
Měli byste použít count=0
- to je jednoduchá lseek()
, kdykoli je to možné.
Takhle:
{ dd bs=1131 skip=1 count=0; cat; } <filtered.dump >trimmed.dump
dd
bude lseek()
deskriptor vstupního souboru s posunem 1131 bajtů a potom cat
jednoduše zkopíruje zbývající část, která zbývá k výstupu.
Dalším způsobem, jak odstranit vedoucí bajty ze souboru (bez použití dd
vůbec), je použití xxd
a sed
respektive tail
.
bytes=$((1131*2))
xxd -p -c 256 filtered.dump | tr -d '\n' | sed "s/^.\{0,${bytes}\}//" | xxd -r -p > trimmed.dump
bytes=$((bytes + 1))
xxd -p -c 256 filtered.dump | tr -d '\n' | tail -c +${bytes} | xxd -r -p > trimmed.dump
@maxschlepzig žádá o online linii. Tady je jeden v Perlu. Trvá to 2 argumenty: Z bajtu a délky. Vstupní soubor musí být zadán jako '<' a výstup bude na výstupu:
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 < bigfile > outfile
Pokud je délka větší než soubor, zkopíruje se zbytek souboru.
V mém systému to přináší 3,5 GB/s.