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. Commented Apr 30, 2010 at 20:14
  • Not sure what you mean by "xdelta". There's no such command on my shell... 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. 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). 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
Sign up to request clarification or add additional context in comments.

3 Comments

Sweet! But as you can see in my answer below, that would probably be overkill. Still, nice to know.
If you know the integer(s) in advance, you can simplify this quite a bit, e.g. printf "\x30"
@Gordon: That's true see the OP's answer. However, the whole purpose of this function is versatility.
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 Comments

This seems to work. I'll try to figure it out and post my results.
echo is disturbingly nonportable -- for the example above, some implementations will just print -e \x30, which isn't what you want at all.
Weird, wouldn't have thought so. Luckily the script is already running without problems.
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...?
I suggest to use printf instead of echo -n -e: printf \\x30
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

Comments

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 Comments

Beware of the many problems with echo portability; it seems to be a serious thing. printf should be considered most of the time.
Correct. This was already mentioned by @Dennis Williamson in a separate answer: stackoverflow.com/a/2747374/219324
I would like to note that endianness conversion has been done manually in that answer.
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.

Comments

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 Comment

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

Comments

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.

Comments

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 Comment

I thought of that but I don't want to use an extra file for 4 bytes.
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.