读取大文件的中间


19

我有一个1 TB的文件。我想从字节12345678901读取到字节19876543212,并将其放在具有100 MB RAM的计算机上的标准输出上。

我可以轻松地编写一个执行此操作的perl脚本。sysread提供700 MB / s(可以),但是syswrite仅提供30 MB / s。我想要更高效的东西,最好是在每个Unix系统上安装的东西,并且可以以1 GB / s的速度传递。

我的第一个想法是:

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

但这不是有效的。

编辑:

我不知道我怎么测量syswrite错误。这提供了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

并避免yes | dd bs=1024k count=10 | wc噩梦。


您的命令与bs=1M iflag=skip_bytes,count_bytes
frostschutz 2014年

Answers:


21

由于块大小小,这很慢。使用最新的GNU ddcoreutils v8.16 +),最简单的方法是使用skip_bytesand 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"

更新资料

fullblock选项根据@Gilles答案在上方添加。起初,我认为它可能由暗示count_bytes,但事实并非如此。

下面提到的问题是一个潜在的问题,如果dds的读/写调用由于任何原因而中断,则数据将丢失。在大多数情况下,这种情况不太可能(由于我们从文件而不是管道中读取数据,因此几率有所降低)。


使用dd不带skip_bytesand count_bytes选项的a更困难:

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

您也可以尝试使用不同的块大小,但是增益不会非常显着。参见- 有没有一种方法可以确定dd的bs参数的最优值?


@Graeme如果bs不是一个因素,第二种方法不会失败skip吗?
史蒂文·彭妮

@StevenPenny,不确定您要获得什么,但是skip有很多块,而不是字节。也许您很困惑,因为skip_bytes在第一个示例中使用的含义skip 在那里的字节数?
Graeme

bs4,096,这意味着您无法更准确地跳过该4,096字节
史蒂文·彭妮

1
@StevenPenny,这就是为什么要使用三个不同的运行方式来复制dd第一个和最后一个未使用bs=1块对齐的数据的原因。
Graeme 2014年

6

bs=1告诉一次dd读取和写入一个字节。每个readand write调用都有开销,这使它变慢了。使用较大的块大小以获得良好的性能。

当您复制整个文件,在Linux下至少,我发现,cpcat比快dd,即使你指定一个较大的块大小。

要仅复制文件的一部分,可以通过管道传输tailhead。这需要GNU coreutils或其他一些head -c必须复制指定数量的字节的实现(tail -c在POSIX中,但head -c不是)。在Linux上进行的快速基准测试表明,该速度比慢dd,这可能是由于管道的原因。

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

问题dd在于它不可靠:它可以复制部分数据。据我所知,dd在读取和写入常规文件时是安全的—请参阅dd什么时候适合复制数据?(或者,当read()和write()是局部的) —但前提是它不会被signal中断。使用GNU coreutils,您可以使用该fullblock标志,但这不是可移植的。

另一个问题dd是,很难找到有效的块计数,因为跳过的字节数和传输的字节数都必须是块大小的倍数。您可以使用多个调用dd:一个用于复制第一个部分块,一个用于复制大部分对齐的块,一个用于复制最后一个部分的块- 有关外壳程序片段,请参见Graeme的答案。但是请不要忘记运行脚本时,除非使用fullblock标志,否则您需要祈祷dd能够复制所有数据。dd如果副本是部分副本,则返回非零状态,因此很容易检测到该错误,但是没有实际的修复方法。

POSIX在shell级别上没有更好的选择。我的建议是编写一个小型的专用C程序(取决于您实现的确切方式,您可以称其为dd_done_rightor tail_headmini-busybox)。


哇,我以前从来都不知道这个yes | dd bs=1024k count=10 | wc问题。讨厌。
Ole Tange 2014年

4

dd

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

或者使用losetup

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

然后dd,,cat...循环设备。


这似乎非常以Linux为中心。我也需要相同的代码才能在AIX,FreeBSD和Solaris上工作。
Ole Tange 2014年

0

这是您可以执行的操作:

   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

这就是所有真正需要的内容-不需要太多。首先dd count=0 skip=1 bs=$block_size1lseek()通过普通的文件输入瞬间实用。不会丢失任何数据或任何其他不实信息,您可以直接寻找所需的起始位置。由于文件描述符是由Shell拥有的,而dds只是继承它,因此它们会影响其光标位置,因此您可以逐步使用它。确实非常简单-没有比它更适合该任务的标准工具了dd

这使用了通常理想的64k块大小。与流行的看法相反,较大的块大小不会使dd工作更快。另一方面,微小的缓冲区也不行。dd需要在系统调用中同步其时间,这样它就不必等待将数据复制到内存中并再次复制出去,而且也不必等待系统调用。因此,您希望它花费足够的时间,以使下一个read()不必等待最后一个,而不会花太多时间以至于要缓冲的大小超出了必要。

因此,第一个dd跳到起始位置。这需要时间。您可以在此时调用您喜欢的任何其他程序以读取其stdin,它将直接在所需的字节偏移处开始读取。我叫另一个dd读取((interval / blocksize) -1)计数块到标准输出。

最后需要做的就是复制上一个除法运算的模数(如果有)。就是这样。

顺便说一句,当人们在没有证据的情况下陈述事实时,请不要相信。是的,可以进行dd简短读取(尽管从正常的块设备读取该名称是不可能的,但这样的事情是不可能的)。仅当您没有正确缓冲dd从块设备以外的设备读取的流时,此类操作才有可能。例如:

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

在这两种情况下,dd复制所有数据。在第一种情况下,可能(虽然不太可能有cat,一些输出块,其中dd拷贝出来的意愿位等于“$ NUM”字节,因为dd是只具备只有在所有缓冲任何缓冲区时,特别是在它的命令-要求线。bs=表示最大块大小,因为目的dd是实时的I / O。

在第二个示例中,我明确指定输出块大小并dd缓冲读取,直到可以完成写入为止。这并不影响count=哪个基于输入块,但是为此,您只需要另一个dd。否则,您给与的任何错误信息都将被忽略。

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.