7

Given this file

$ cat hello.txt
hello doge world

I would like to remove a range of bytes to end up with this

$ cat hello.txt
heorld

I would like to do this with dd if possible. The reason is because I am already using dd to overwrite bytes in this manner

printf '\x5E' | dd conv=notrunc of=hello.txt bs=1 seek=$((0xE))

I prefer to write back to the same file, but a different output file would be okay.

Zombo
  • 1
  • 5
  • 44
  • 63
  • 1
    Do you have a recent version of GNU dd? If so, you can seek the input and output with byte granularity, so a modified version of this answer followed by a call to truncate to shorten the file ought to work. – Mark Plotnick May 11 '14 at 04:38
  • 1
    A word of warning: if you overwrite the existing file and your system crashes half-way through, the file will be in an unusable state: you'll have no way to know which part has already been moved and which part hasn't. There is a reason why most tools are geared towards creating new files! – Gilles 'SO- stop being evil' May 11 '14 at 22:49

4 Answers4

7

It is a matter of specifying blocksize, count, and skip:

$ cat hello.txt
hello doge world
$ { dd bs=1 count=2 ; dd skip=3 bs=1 count=1 ; dd skip=6 bs=1 ; } <hello.txt 2>/dev/null
he orld

The above uses three invocations of dd. The first gets the first two characters he. The second skips to the end of hello and copies the space which follows. The third skips into the last word world copying all but its first character.

This was done with GNU dd but BSD dd looks like it should work also.

John1024
  • 74,655
5
# copy the end piece into correct position
dd bs=1 seek=2 skip=12 conv=notrunc if=hello.txt of=hello.txt

# truncate
dd bs=1 seek=6 if=/dev/null of=hello.txt

Mark is right

Zombo
  • 1
  • 5
  • 44
  • 63
4

I guess this is possible using dd but that's kind of like using a tank to kill a fly. Why not

$ printf "%s %s\n" $(head -c 2 hello.txt) $(tail -c 5 hello.txt )
he orld

The -c option means (for head):

   -c, --bytes=[-]K
          print the first K bytes of each  file;  with  the  leading  '-',
          print all but the last K bytes of each file

and for tail:

   -c, --bytes=K
          output the last K bytes; alternatively,  use  -c  +K  to  output
          bytes starting with the Kth of each file

In general, to remove the byte range n to x inclusive, you would run

( head -c n-1; head -c -x-1)  )

For example, to remove the 4th through 12th bytes:

$ (head -c 3 hello.txt; tail -c +11 hello.txt )
hel world
terdon
  • 242,166
0

Perl pack and unpack function is goot at dealing with fixed-width string. If you want to use Perl, try this:

$ perl -le '
    ($head,$skip,$tail) = unpack("A2 A5 A*", "hello world");
    ($space) = $skip =~ m/(\s+)/;
    print $head.$space.$tail;
'
he orld

Explanation

  • We will divide string to three parts, $head is start of string until the first byte we want to remove, $skip is the range of bytes we want to remove, $tail is the rest of string.

  • unpack template "A2 A5 A*" will divide the string to three parts as explained above.

  • With $skip, we will get any spaces in it, save it to $space.

  • Print the concatenation of three parts to get desired output.

Updated

Since you don't want to save the space, the solution seems to be easier:

$ perl -le 'print unpack("A2 x5 A*","hello world")'
heorld

With updated string:

$ perl -le 'print unpack("A2 x10 A*","hello doge world")'
heorld

x10 in unpack template meaning skip the 10 bytes in string.

cuonglm
  • 153,898