If nothing else, you need a case statement.
random(){
printf "%d^${2##*[!0-9]*}\n" "$(($(
export LC_ALL=C; a=$1
while x=${a%"${a#?}"} s=
case $a in
([a-z]-["$x"-z]*|[A-Z]-["$x"-Z]*)
a=${a#??} s=?
printf '(%d-%d+1)+' "'$a" "'$x";;
([0-9]-["$x"-9]*)
x=${a%"${a#???}"} a=${a#"$x"}
printf "$((-($x)+1))+";;
(?-*) a=${a#??}
echo 2+;;
(?*) x=${a%%[A-Za-z0-9]-*}
a=${a#"$x"}
echo "${#x}+";;
(*) ! echo 0 ;;esac
do a=${a#$s} ; done
)))" | bc| sed 's/$/ possibilities./
/^1 /s/....$/y./'
}
Ok, I have to apologize - I only now realized that what I thought were char classes were literal arg strings - and that you were parsing them. Now I understand what you're about. I made it work just now - it will actually do the ranges, and you can handle any kind of character - because it counts the difference.
{ random A 1 #1 char and 1
random AB 1 #2 chars and 1
random a-Z 1 #3 chars because the range is invalid and 1
random aa-c 1 #4 chars: aabc and 1
random a-c 2 #3 chars: abc and 2
random aa-z 3) #27 chars:aa-z and 3
}
OUTPUT
1 possibility.
2 possibilities.
3 possibilities.
4 possibilities.
9 possibilities.
19683 possibilities.
The first thing that is done is we try to printf our second argument - which undergoes ${2##*[!0-9]*} parameter expansion to render it null if it contains any non-numeric character - preceded by the %digit value of an arithmetic expansion in which our entire run loop occurs. In fact, the entire function is one printf statement from the perspective of the current shell.
But within the $((math expansion)) is a $(command substitution) - and that is all. We provide it with no numbers - it is just $(($())).
Except that the subshell within the command substitution runs our loop and collects our output. Our output eventually adds up to a viable arithmetic expression.
Within the loop we always put $a's first char in $x at the top of each. Next we check the head of our current value for $a against some case patterns. The first of these looks like this:
([a-z]-["$x"-z]*|[A-Z]-["$x"-Z]*)
Here we're using $a's first character - which we saved in $x - twice. In the first place it is the argument against which we compare our pattern. But in the second place it is a limiter for our $a's third character - it ensures that not only does the third character fit within an alphabetics range, but also that it is greater than or equal to $a's first character. Because z-a is an invalid range and doesn't match when we use $x to test.
If we match this pattern we printf the ascii digit value of each of $x and $a after we strip from the head $a two characters.
Like this:
printf '(%d-%d+1)+' "'$x" "'$a"
...which becomes a paranthetical subexpression in our end arithmetic, and evaluates to the distance between our range.
We do the same thing for the digits:
([0-9]-["$x"-9]*)
And we strip the head off $a in that case as well. The math for the digits is a little different - we get the negative. $a's first three chars are saved in $x, like x=4-9.
And so we print:
printf "$((-($x)+1))+"
In the event that the head of $a looks like a range but still managed to fail the previous two tests, and so matches our (?-*) pattern, then we'll strip the top two characters, echo 2+, and cycle again.
If we make it as far down as the pattern (?*) then we will strip from the head of $a everything up to the first occurring possible range match in $a. If we're successful we'll continue to loop and just split the difference, but if there is no possible range match in the remainder of $a, we'll quit the loop because we'll consume all of $a right here and echo ${#a}+, basically.
Well, we won't quit it yet - the loop doesn't ever quit until $a is sent back to the top of the loop empty. When that happens we do ! echo 0 which is the last string we write to output.
And so come back to the current shell and evaluate everything in the math expansion to print "$((subshell_math))^$2" at bc whose output is piped to sed which appends the string possibles. to its output.
And that's how it works.