从文件开头删除字节的最佳方法?


61

今天,我不得不从800MB的混合文本/二进制文件中删除前1131个字节,这是我为新存储库准备的经过过滤的Subversion转储。最好的方法是什么?

首先我尝试

dd bs=1 skip=1131 if=filtered.dump of=trimmed.dump

但是在跳过之后,这一次将文件的其余部分复制了一个字节,即非常慢。最后,我计算出需要405个字节将其四舍五入为512个可以跳过的三个块

dd if=/dev/zero of=405zeros bs=1 count=405
cat 405zeros filtered.dump | dd bs=512 skip=3 of=trimmed.dump

哪个完成得很快,但肯定有一个更简单/更好的方法?还有我遗忘的其他工具吗?谢谢!


dd是完成这项工作的正确工具-看起来您想出了一个很好,优雅的解决方案。
贾斯汀·埃斯蒂尔

Answers:


62

您可以切换bs并跳过选项:

dd bs=1131 skip=1 if=filtered.dump of=trimmed.dump

这样,操作可以受益于更大的块。

否则,您可以尝试使用tail(尽管将其与二进制文件一起使用并不安全):

tail -c +1132 filtered.dump >trimmed.dump

最后,您可以使用3个dd实例编写如下内容:

dd if=filtered.dump bs=512k | { dd bs=1131 count=1 of=/dev/null; dd bs=512k of=trimmed.dump; }

第一个dd打印其标准输出filter.dump;第二个只读取1131个字节并将其丢弃;然后,最后一个从其标准输入中读取filter.dump的其余字节,并将它们写入trimped.dump。


6
谢谢!我不知道管道输入会延续到第二个过程中-太整齐了。我不敢相信我没想到bs=1131 skip=1:-/
Rup

2
Shell实用程序的大多数现代实现都可以正确地与二进制文件一起使用(即,它们在使用null字符时没有问题,并且不会在文件末尾插入额外的换行符)。当然,GNU和* BSD实现是安全的。
Gilles

17

不确定何时skip_bytes添加,但是要跳过前11个字节,请执行以下操作:

# 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

where iflag=skip_bytes告诉dd将skip选项的值解释为字节而不是块,使其简单明了。


对于大型文件和要删除的少量数据,无疑具有速度优势。
sstn

这是最好的答案,因为它适用于每个块大小,例如iflag=skip_bytes skip=1234 bs=1M
phiresky

15

您可以使用一个子shell和两个dd调用,如下所示:

$ ( 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

1
谢谢-我不知道管道输入是否会继续进行第二步处理,我想那是子外壳程序吗?我一定会记得的!我给Marco了个勾号,因为他先是在这里,但+1并感谢您的回答!
Rup

1
@Rup,是的,通过括号创建的子外壳提供了一个stdin文件描述符,并且两个dd调用都连续使用它的输入。是的
-Marco

6

如果文件系统和Linux内核支持它,那么您可以尝试fallocate是否要进行更改:在最好的情况下,根本没有数据IO:

$ fallocate <magic> -o 0 -l 1131 inplace.dump

其中<magic>取决于文件系统,Linux版本和文件类型(FALLOC_FL_COLLAPSE_RANGEFALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE可以在内部使用)。


1
这是我的首选方法,但是在容器中运行它会出现问题。 stackoverflow.com/questions/31155591/…–
michaelcurry

3

您应该使用count=0- lseek()尽可能简单。

像这样:

{  dd bs=1131 skip=1 count=0; cat; } <filtered.dump >trimmed.dump

ddlseek()输入文件描述符的偏移量设置为1131字节,然后cat将仅复制其余内容以输出。


2

从文件中删除前导字节(dd根本不使用)的另一种方法是分别使用xxdsedtail

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

很好,但是我认为我更喜欢只使用二进制文件而不是将其与十六进制进行转换。
Rup

2

@maxschlepzig要求在线班轮。这是perl中的一个。它需要2个参数:从字节和长度。输入文件必须由'<'给出,输出将在stdout上:

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

如果长度大于文件长度,则文件的其余部分将被复制。

在我的系统上,这提供了3.5 GB / s的速度。


我认为他的一线挑战是让您证明脚本语言解决方案比他的单线shell解决方案要好。我更喜欢他:它对我来说更短,更清晰。如果您的性能更好,那是因为您使用的块大小比他大,而在他的版本中也很容易升级。
Rup

@Rup Alas,但是没有。您似乎忘记了dd这不能保证完整阅读。尝试:是| dd bs = 1024k count = 10 | 厕所unix.stackexchange.com/questions/17295/...
奥莱丹

同样,我的解决方案不会读取不需要的字节(长度可能为数TB)。
Ole Tange'4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.