You can read and write binary, including null, in pure bash, without even subshells, let alone external processes like dd and xxd.
This is a driver/client for a vintage portable disk drive that has an rs232 interface. Any disk drive obviously has to handle arbitrary data including binary including 0x00.
It's entirely in native bash with no external tools and not even any subshells. It does have to call stty and mkfifo once at startup, but that's just startup like to open the serial port, not part of the work.
The gimmick for reading nulls is not exactly efficient. Basically you read one character at a time and look at the errorlevel from read to detect the difference between "got nothing" and "got 0x00"
It's fine for this case because the drive is so slow and small that even the entire disk is only 200k max. Making bash read one byte at a time for 200k is still nothing today but only because hardware is insane today.
But it's possible. And the beginning of the post does say "as a thought experiment".
Similarly, you don't need xxd to convert back & forth between raw binary and text encoding.
My own similar thought experiment included the idea that, if I'm not going to use any external process I can possibly avoid and only use internal bash features, on the flip side of that, I will squeez bash for all it's worth, allow every bashism possible.
printf % codes and brace expansions, and printf -v to write directly to variables without needing a subshell or a temp file.
See file_to_fhex() for an example of reading binary into hex pairs.
That example is simpler to read than tpdd_read() because reading from a file is simpler than reading from a stream wrt detecting eof.
read a byte, use printf to convert that byte to a hex pair. The funny looking syntax with a single ' in front of the variable is important.
See tpdd_write() for writing hex pairs back out to binary.
brace-expand "aa bb cc" to "\xaa\xbb\xcc", feed that to printf to output binary.
All through this script I'm using arrays instead of simple variables to hold the hex pairs. That's not necessary, I'm just doing that because this app needs to do a lot of byte/position counting on the data, both for parsing the output from the drive and for sending commands to the drive. It's all packets of fixed length fields.
Meanwhile I at least 50% want to apologize for it.
I know full well that it is crammed full of inscrutabilities, relies on side effects, implicit behaviours, an ocean of globals & other state, and isn't even fully consistent with internal conventions.
This would be doing a bad job at work.
Some of that is just working with what you have, like the globals are how you avoid needing subshells so that's justified.
But there's other things like a lot of "business logic" is implimented in the form of arcane conditional brace expansions and particular return values of some commands etc, that could have been done more scrutably with ordinary readable explicit logic, just somewhat more verbosely.
I'm the first to say it aint readable. It's really a great example of "just because you CAN do something..."
But I wanted no dependencies (that aren't likely already there), cross platform, doesn't break in 6 months simply because 6 months have passed, yet interpreted vs a c program. Awk would tick the same boxes. I have both awk and ksh scripts I wrote in the 90's on xenix that still work the same today on linux & mac & bsd, meanwhile a python script breaks in a year.
This is a driver/client for a vintage portable disk drive that has an rs232 interface. Any disk drive obviously has to handle arbitrary data including binary including 0x00.
It's entirely in native bash with no external tools and not even any subshells. It does have to call stty and mkfifo once at startup, but that's just startup like to open the serial port, not part of the work.
https://github.com/bkw777/pdd.sh
The gimmick for reading nulls is not exactly efficient. Basically you read one character at a time and look at the errorlevel from read to detect the difference between "got nothing" and "got 0x00"
It's fine for this case because the drive is so slow and small that even the entire disk is only 200k max. Making bash read one byte at a time for 200k is still nothing today but only because hardware is insane today.
But it's possible. And the beginning of the post does say "as a thought experiment".
Similarly, you don't need xxd to convert back & forth between raw binary and text encoding.
My own similar thought experiment included the idea that, if I'm not going to use any external process I can possibly avoid and only use internal bash features, on the flip side of that, I will squeez bash for all it's worth, allow every bashism possible.