I have a 1 TB file. I would like to read from byte 12345678901 to byte 19876543212 and put that on standard output on a machine with 100 MB RAM.

I can easily write a perl script that does this. sysread delivers 700 MB/s (which is fine), but syswrite only delivers 30 MB/s. I would like something more efficient, preferably something that is installed every Unix system and that can deliver in the order of 1 GB/s.

My first idea is:

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

But that is not efficient.


I have no idea how I measured syswrite wrong. This delivers 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

and avoids the yes | dd bs=1024k count=10 | wc nightmare.

  • 1
    your command with bs=1M iflag=skip_bytes,count_bytes Mar 27, 2014 at 17:34

4 Answers 4


This is slow because of the small block size. Using a recent GNU dd (coreutils v8.16 +), the simplest way is to use the skip_bytes and count_bytes options:



copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"


fullblock option added above as per @Gilles answer. At first I thought that it might be implied by count_bytes, but this is not the case.

The issues mentioned are a potential problem below, if dds read/write calls are interrupted for any reason then data will be lost. This is not likely in most cases (odds are reduced somewhat since we are reading from a file and not a pipe).

Using a dd without the skip_bytes and count_bytes options is more difficult:



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"

You could also experiment with different block sizes, but the gains won't be very dramatic. See - Is there a way to determine the optimal value for the bs parameter to dd?

  • @Graeme wont the second method fail if bs is not a factor of skip?
    – Zombo
    May 11, 2014 at 17:09
  • @StevenPenny, not sure what you are getting at, but skip is a number of blocks, not bytes. Maybe you are confused since skip_bytes is used in the first example meaning skip is in bytes there?
    – Graeme
    May 12, 2014 at 0:59
  • Your bs is 4,096, which mean that you cannot skip more accurately that 4,096 bytes
    – Zombo
    May 12, 2014 at 1:14
  • 1
    @StevenPenny, this is why there are three different runs of dd with the first and last using bs=1 in order to copy the data which does not start or finish on a block alignment.
    – Graeme
    May 12, 2014 at 10:11
  • Update: Coreutils 9.1 introduces new syntax for the count_bytes and so on options: count=100KiB. iflag=skip_bytes is deprecated but still supported, in favour of skip=123456B (How can I copy arbitrary part of binary file with reasonable speed? quotes the changelog) Mar 29, 2023 at 4:36

bs=1 tells dd to read and write one byte at a time. There is an overhead for each read and write call, which makes this slow. Use a larger block size for decent performance.

When you copy a whole file, at least under Linux, I've found that cp and cat are faster than dd, even if you specify a large block size.

To copy only part of a file, you can pipe tail into head. This requires GNU coreutils or some other implementation that has head -c to copy a specified number of bytes (tail -c is in POSIX but head -c isn't). A quick benchmark on Linux shows this to be slower than dd, presumably because of the pipe.

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

The problem with dd is that it is not reliable: it can copy partial data. As far as I know, dd is safe when reading and writing to a regular file — see When is dd suitable for copying data? (or, when are read() and write() partial) — but only as long as it isn't interrupted by a signal. With GNU coreutils, you can use the fullblock flag, but this is not portable.

Another problem with dd is that it can be hard to find a block count that works, because both the number of skipped bytes and the number of transferred bytes count need to be a multiple of the block size. You can use multiple calls to dd: one to copy the first partial block, one to copy the bulk of aligned blocks and one to copy the last partial block — see Graeme's answer for a shell snippet. But don't forget that you when you run the script, unless you're using the fullblock flag, you need to pray that dd will copy all the data. dd returns a nonzero status if a copy is partial, so it's easy to detect the error, but there's no practical way to repair it.

POSIX has nothing better to offer at the shell level. My advice would be to write a small special-purpose C program (depending on exactly what you implement, you can call it dd_done_right or tail_head or mini-busybox).

  • Wow, I never knew the yes | dd bs=1024k count=10 | wc problem before. Nasty.
    – Ole Tange
    Mar 28, 2014 at 20:31

With dd:

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

Alternatively with losetup:

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

And then dd, cat, ... the loop device.

  • This seems very Linux centric. I need the same code to work on AIX, FreeBSD, and Solaris, too.
    – Ole Tange
    Mar 27, 2014 at 21:08

This is how you do you can do this:

   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

That's all that is really necessary - it doesn't require much more. In the first place dd count=0 skip=1 bs=$block_size1 will lseek() over regular file input practically instantaneously. There is no chance of missed data or whatever other untruths are told about it, you can just seek directly to your desired start position. Because the file descriptor is owned by the shell and the dd's are merely inheriting it, they will affect its cursor position and so you can just take it in steps. It really is very simple - and there is no standard tool better suited to the task than dd.

That uses a 64k blocksize which is often ideal. Contrary to popular belief, larger blocksizes do not make dd work faster. On the other hand, tiny buffers are no good either. dd needs to synchronize its time in system calls so that it need not wait on copying data into memory and out again, but also so that it need not wait on system calls. So you want it to take enough time that the next read() doesn't have to wait on the last, but not so much that you're buffering in larger sizes than is necessary.

So the first dd skips to the start position. That takes zero time. You could call any other program you liked at that point to read its stdin and it would begin reading directly at your desired byte offset. I call another dd to read ((interval / blocksize) -1) count blocks to stdout.

The last thing that is necessary is to copy out the modulus (if any) of the previous division operation. And that's that.

Don't believe it, by the way, when people state facts on their face without evidence. Yes, it is possible for dd to do a short read (though such things are not possible when reading from a healthy block device - thus the name). Such things are only possible if you do not correctly buffer a dd stream which is read from other than a block device. For example:

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

In both cases dd copies all of the data. In the first case it is possible (though unlikely with cat) that some of the output blocks which dd copies out will bit equal "$num" bytes because dd is spec'd only to buffer anything at all when the buffer is specifically requested on its command-line. bs= represents a maximum block-size because the purpose of dd is real-time i/o.

In the second example I explicitly specify the output blocksize and dd buffers reads until complete writes can be made. That doesn't affect count= which is based on input blocks, but for that you just need another dd. Any misinformation which is given you otherwise should be disregarded.

  • The -1 in the no. of blocks is a bug. It will prevent the last block from being read.
    – Harry
    Jul 7, 2022 at 1:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .