I need to make a fairly simple bash script to pull bytes one at a time from a binary file, send it out a serial port, and then wait for a byte to come back before I send the next one. This is effectively for an EEPROM programmer, where an off the shelf solution won't work.

I'm mostly stuck at pulling the bytes out of the file into a variable, beyond that I know I can echo the values down the serial port and read them back with dd. (Is there a better way to do that also?)

Thanks for the help!

  • will the return byte definitely always arrive singly? As in, will you receive only one byte per byte sent?
    – mikeserv
    Commented Sep 11, 2014 at 17:31
  • You can work with hex values, like using the command xxd -ps and xxd -r -ps. It would be safer.
    – LatinSuD
    Commented Sep 11, 2014 at 22:01
  • IIRC bash can't cope with null bytes, so it's unsuitable to deal with binary data. Most shells aren't designed to deal with binary data (zsh is an exception). Is this an embedded system? If so how restricted is your tool set? Commented Sep 12, 2014 at 15:00
  • Yeah, the device on the far end should relay the same byte that is sent out, after it has gone through a bit of processing on the device end.
    – Minifig666
    Commented Sep 12, 2014 at 21:37
  • This script is just going on a lubuntu laptop, so I have no real restrictions on the tools I can use. It would be nice to send straight binary values down the serial port, but I could potentially send two ASCII hex characters per byte, and then edit the code on the device end to cope.
    – Minifig666
    Commented Sep 12, 2014 at 21:41

4 Answers 4


Use dd or xxd (part of Vim), for example to read one byte (-l) at offset 100 (-s) from binary file try:

xxd -p -l1 -s 100 file.bin

to use hex offsets, in Bash you can use this syntax $((16#64)), e.g.:

echo $((16#$(xxd -p -l1 -s $((16#FC)) file.bin)))

which reads byte at offset FC and print it in decimal format.

Alternatively use dd, like:

dd if=file.bin seek=$((16#FC)) bs=1 count=5 status=none

which will dump raw data of 5 bytes at hex offset FC.

Then you can assign it into variable, however it won't work when your data has NULL bytes, therefore either you can skip them (xxd -a) or as workaround you can store them in plain hexadecimal format.

For example:

  1. Read 10 bytes at offset 10 into variable which contain bytes in hex format:

    hex=$(xxd -p -l 10 -s 10 file.bin)
  2. Then write them into file or device:

    xxd -r -p > out.bin <<<$hex

Here are few useful functions in Bash:

set -e

# Read single decimal value at given offset from the file.
read_value() {
  [ -n "$file" ]
  [ -n "$offset" ]
  echo $((16#$(xxd -p -l1 -s $offset "$file")))

# Read bytes in hex format at given offset from the file.
read_data() {
  xxd -p -l $count -s $offset "$file"

Sample usage:

read_value file.bin $((16#FC)) # Read 0xFC offset from file.bin.
  • I think your dd example is wrong, it should use skip= instead of seek= because seek= is of the outputfile whiel skip is for the input file.
    – uli42
    Commented May 23, 2023 at 11:14

I’m not sure I understand the big picture of what you’re asking for, but I have a couple of approaches that you might want to consider:

Use dd to read your input file

exec 3< input_file
while true
    dd bs=1 count=1 <&3 > this_byte
    if ! [ -s this_byte ]       # If size is zero, we’re done.
    code using the this_byte file

Use od to digest your input file

od -bv input_file | while read a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16
do                              # Skip $a0; it’s the address.
    for byte_val in "$a1" "$a2" "$a3" "$a4" "$a5" "$a6" "$a7" "$a8" "$a9" "$a10" \
                                "$a11" "$a12" "$a13" "$a14" "$a15" "$a16"
        if [ "$byte_val" = "" ]
        code using the $byte_val value
        for example: echo -e "\0$byte_val\c"

This is probably easier to do with a tiny C program, or an (almost) one-liner in a scripting language like Python or Perl.

The Unix shell is an interpreter for user commands, that (for convenience in writing repetitive commands) has rudimentary facilities for programming. It often gets (mis)used because a version of the Bourne shell is guaranteed to be available on any Unix system, not because it is a particularly good (or even half-way decent) programming language.


You can use perl instead:

$ printf '%s%s%s' 'ăâé' | LC_ALL=C.UTF-8 perl -Mopen=locale -ne '
    BEGIN { $/ = \1 }
    printf "%s\n", $_;
  • You're just printing the same byte back? As I understand it the asker wants to know how to read a byte from one input, print it to output, and then to wait for another byte from a different input before continuing the loop. Unless that's already what you do and I don't get it - which is entirely possible.
    – mikeserv
    Commented Sep 11, 2014 at 19:09

You must log in to answer this question.

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