As a shell solution, getopts
is probably easiest. The thing about getopts
is that it is POSIX-specified to do exactly what you're asking - process a byte-stream in a shell-loop. I know that sounds weird, because, if you're like me before I learned this, you're probably thinking, well, gee, I thought it was supposed to handle command-line switches. Which is true, but so is the first thing. Consider:
-thisisonelongstringconsistingofseparatecommandlineswitches
Yes, getopts
has to handle that. It has to split that char by char in a loop and return to you each character in either the shell variable $OPTARG
or in another that you specify by name all depending on how specific you get when you call it. What's more, it has to return errors in shell variables and save its progress when it does in the shell variable $OPTIND
so it can resume right where it left off if you can somehow address it. And it has to do the whole job without invoking a single subshell.
So let's say we have:
arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done
Hmmm.... I wonder if it worked?
echo "$((${#arg}/6))" "$#"
482 482
That's nice...
eval '
printf %.1s\\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4
So, as you can see, the getopts
command completely set the array for every sixth byte in the string. And it doesn't have to be numbers like this - nor must it even be shell safe characters - and you needn't even specify the target chars as I did above with 01234565789
either. I've tested this repeatedly in a lot of shells and they all just work. There are some quirks - bash
will throw away the first character if it is a whitespace character - dash
accepts the :
colon as a specified parameter even though it is just about the only POSIX specifically forbids. But none of that matters because getopts
still deposits the current opt char's value in $OPTARG
even when it returns you an error (represented by a ? assigned to your specified opt var) and otherwise explictly unsets $OPTARG
unless you've declared an option should have an argument. And the whitespace thing is kind of a good thing - it only discards a leading space, which is excellent, because, when working with unknown values, you can do:
getopts : o -" $unknown_value"
...to kick off the loop without any danger of the first character actually being in your accepted args string - which would result in getopts
sticking the whole thing in $OPTARG
at once - as an argument.
Here's another example:
OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"
do printf '\\%04o' "'$OPTARG"; done
\0040\0150\0071\0365\0320\0070\0161\0064\0274\0115\0012\0215\0222\0271\0146\0057\0166
I set $OPTIND=1
in the first line because I just used getopts
and, until you reset it, it expects its next call to continue where it left off - it wants "${arg2}"
in other words. But I don't feel like giving and I'm doing a different thing now, so I let it know by resetting $OPTIND
at which point it's good to go.
In this one I used zsh
- which doesn't quibble about a leading space - and so the first character is octal 40 - the space character. I don't usually use getopts
in that way, though - I usually use it to avoid doing a write()
for each byte and instead assign its output - which comes in a variable - to another shell variable - as I did above with set
after a fashion. Then, when I'm ready I can take the whole string and when I do usually strip the first byte.