19

I have a file with binary data and I need to replace a few bytes in a certain position. I've come up with the following to direct bash to the offset and show me that it found the place I want:

dd bs=1 if=file iseek=24 conv=block cbs=2 | hexdump

Now, to use "file" as the output:

echo anInteger | dd bs=1 of=hextest.txt oseek=24 conv=block cbs=2

This seems to work just fine, I can review the changes made in a hex editor. Problem is, "anInteger" will be written as the ASCII representation of that integer (which makes sense) but I need to write the binary representation.

I want to use bash for this and the script should run on as many systems as possible (I don't know if the target system will have python or whatever installed).

How do I tell the command to convert the input to binary (possibly from a hex)?

5
  • You may want to use xdelta for this - it's common enough that it may be 'universal' for you.
    – MikeyB
    Commented Apr 30, 2010 at 20:14
  • Not sure what you mean by "xdelta". There's no such command on my shell...
    – Max Leske
    Commented May 1, 2010 at 6:39
  • xxd -r is designed for this purpose Commented May 4, 2018 at 14:08
  • @Jezz Yes. See stackoverflow.com/a/8199163/219324.
    – Max Leske
    Commented May 6, 2018 at 8:27
  • The question asked is unix.stackexchange.com/questions/118247/echo-bytes-to-a-file though that's not what OP is trying to do (xy problem). xxd seems like what OP wants, not really what OP asks (as it will not use native endianess).
    – MayeulC
    Commented Sep 29, 2021 at 13:01

10 Answers 10

19

printf is more portable than echo. This function takes a decimal integer and outputs a byte with that value:

echobyte () {
    if (( $1 >= 0 && $1 <= 255 ))
    then
        printf "\\x$(printf "%x" $1)"
    else
        printf "Invalid value\n" >&2
        return 1
    fi
}

$ echobyte 97
a
$ for i in {0..15}; do echobyte $i; done | hd
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010
3
  • Sweet! But as you can see in my answer below, that would probably be overkill. Still, nice to know.
    – Max Leske
    Commented Apr 30, 2010 at 20:17
  • 1
    If you know the integer(s) in advance, you can simplify this quite a bit, e.g. printf "\x30" Commented Apr 30, 2010 at 22:56
  • @Gordon: That's true see the OP's answer. However, the whole purpose of this function is versatility. Commented Apr 30, 2010 at 23:45
13

You can use echo to emit specific bytes using hex or octal. For example:

echo -n -e \\x30 

will print ascii 0 (0x30)

(-n remove trailing newline)

5
  • This seems to work. I'll try to figure it out and post my results.
    – Max Leske
    Commented Apr 30, 2010 at 18:48
  • 4
    echo is disturbingly nonportable -- for the example above, some implementations will just print -e \x30, which isn't what you want at all. Commented Apr 30, 2010 at 22:41
  • Weird, wouldn't have thought so. Luckily the script is already running without problems.
    – Max Leske
    Commented May 1, 2010 at 6:37
  • why, oh why, isn't there a simple way to do this with decimal numbers? I'm converting to hex first - echo -ne \\x$(printf '%x' 65) surely there has to be a better way...?
    – starfry
    Commented Nov 9, 2014 at 14:23
  • I suggest to use printf instead of echo -n -e: printf \\x30 Commented May 4, 2018 at 14:06
8

xxd is the better way. xxd -r infile outfile will take ascii hex-value in infile to patch outfile, and you can specify the specific position in infile by this: 1FE:55AA

4

Worked like a treat. I used the following code to replace 4 bytes at byte 24 in little endian with two integers (1032 and 1920). The code does not truncate the file.

echo -e \\x08\\x04\\x80\\x07 | dd of=<file> obs=1 oseek=24 conv=block,notrunc cbs=4

Thanks again.

3
  • Beware of the many problems with echo portability; it seems to be a serious thing. printf should be considered most of the time. Commented Aug 18, 2021 at 8:07
  • Correct. This was already mentioned by @Dennis Williamson in a separate answer: stackoverflow.com/a/2747374/219324
    – Max Leske
    Commented Aug 19, 2021 at 11:28
  • 1
    I would like to note that endianness conversion has been done manually in that answer.
    – MayeulC
    Commented Sep 29, 2021 at 13:33
4

I have a function to do this:

# number representation from 0 to 255 (one char long)
function chr() { printf "\\$(printf '%03o' "$1")" ; return 0 ; }
# from 0 to 65535 (two char long)
function word_litleendian() { chr $(($1 / 256)) ; chr $(($1 % 256)) ; return 0 ; }
function word_bigendian() { chr $(($1 % 256)) ; chr $(($1 / 256)) ; return 0 ; }
# from 0 to 4294967295 (four char long)
function dword_litleendian() { word_lilteendian $(($1 / 65536)) ; word_litleendian $(($1 % 65536)) ; return 0 ; }
function dword_bigendian() { word_bigendian $(($1 / 65536)) ; word_bigendian $(($1 % 65536)) ; return 0 ; }

You can use piping or redirection to catch the result.

1

If you're willing to rely on bc (which is fairly common)

echo -e "ibase=16\n obase=2 \n A1" | bc -q

might help.

1
  • I tried it out. Your code will return a binary which will be written to the file as the ASCII representation of that binary. Nice thought though, thanks.
    – Max Leske
    Commented Apr 30, 2010 at 18:50
1

With bash, "printf" has the "-v" option, and all shell has logical operators.

So here is simplier form in bash :

int2bin() {
  local i=$1
  local f
  printf -v f '\\x%02x\\x%02x\\x%02x\\x%02x' $((i&255)) $((i >> 8 & 255)) $((i >> 16 & 255)) $((i >> 24 & 255))
  printf "$f"
}
1

I'm a bit late, but here is a way to convert many decimal integers to binary with only one command:

echobytes() {
    printf $(printf '\\x%x' "$@")
}

First, the nested printf will only convert the arguments to hexadecimal. The trick is printf will use the same format for all the arguments here. The output will be something like \x16\x1\x9a, and that last string will be interpreted by the outer printf, which will finally output the binary data you want!

There are ways to split input if it exceeds 255, but I did not dig in further since it could depend on endianness.

0

You might put the desired input into a file and use the "if=" option to dd to insert exactly the input you desire.

1
  • I thought of that but I don't want to use an extra file for 4 bytes.
    – Max Leske
    Commented Apr 30, 2010 at 18:48
0

In my case, I needed to go from a decimal numeric argument to the actual unsigned 16-bit big endian value. This is probably not the most efficient way, but it works:

# $1 is whatever number (0 to 65535) the caller specifies
DECVAL=$1
HEXSTR=`printf "%04x" "$DECVAL"`
BYTEONE=`echo -n "$HEXSTR" | cut -c 1-2`
BYTETWO=`echo -n "$HEXSTR" | cut -c 3-4`
echo -ne "\x$BYTEONE\x$BYTETWO" | dd of="$FILENAME" bs=1 seek=$((0xdeadbeef)) conv=notrunc

Not the answer you're looking for? Browse other questions tagged or ask your own question.