2

I'm making a program for generating random numbers, and before it generates, I want to show how many combinations there are. When I do it, I get this:

64k@conroe$ bash random A-Za-z0-9 3
238328
possible combinations
CG1

I want 'possible combinations' to be on the same line as '238328', so I get

238328 possible combinations

Here's the code for said section:

if [ $1 == 'A-Z' ] || [ $1 ==  'a-z' ]
then
    echo "26^$2" | bc 
elif [ $1 == 'A-Za-z' ] || [ $1 ==  'a-zA-Z' ]
then
    echo "52^$2" | bc
elif [ $1 == 'A-Z0-9' ] || [ $1 ==  'a-z0-9' ]
then
    echo "36^$2" | bc
elif [ $1 == 'A-Za-z0-9' ] ||[ $1 == 'a-zA-Z0-9' ]
then
    echo "62^$2" | bc
else
    echo "${#$1}^$2" | bc 

fi
echo "possible combinations"

If there's any way to make this shorter as well, I would love to get suggestions. I'm still relatively new to bash scripting.

Also, is there any possible way to get the echo | bc to output a number separated by commas? Some of the numbers can get really large, i.e for random A-Za-z0-9 10 the number of possible combinations is 839299365868340224. I want that to be comma separated so it shows 839,299,365,868,340,224. Full code:

if [ $1 == 'A-Z' ] || [ $1 ==  'a-z' ]
then
    echo "26^$2" | bc 
elif [ $1 == 'A-Za-z' ] || [ $1 ==  'a-zA-Z' ]
then
    echo "52^$2" | bc
elif [ $1 == 'A-Z0-9' ] || [ $1 ==  'a-z0-9' ]
then
    echo "36^$2" | bc
elif [ $1 == 'A-Za-z0-9' ] ||[ $1 == 'a-zA-Z0-9' ]
then
    echo "62^$2" | bc
else
    echo "${#$1}^$2" | bc 

fi
echo "possible combinations"
if [ $# == 2 ]
then
    < /dev/urandom env LC_CTYPE=C tr -dc $1 | head -c $2 | while read -n 1 x 
    do
    printf "\e[1;36m$x"
    sleep 0.03
    done
    echo -e '\033[0m'
    exit 0
exit 1
elif [ $# == 3 ]
then

for (( c=1; c<=$3; c++ ))
 do {
        < /dev/urandom env LC_CTYPE=C tr -dc $1 | head -c $2 | while read -n 1 x 
    do
    printf "\e[0;33m$x"
    sleep 0.03
    done
    echo -e '\033[0m'
}
done


exit 1
fi
2
  • can you post your entire script to review it?
    – VaTo
    Jun 17 '15 at 20:44
  • @saulortega I'm away right now, I'll post the full script when I get home. Jun 17 '15 at 21:41
2

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.

8
  • Heh, nice edit. I was just about to say that it didn't work with the 0-9 argument. Would you mind explaining the code so I'm able to understand how it actually works? Jun 18 '15 at 1:10
  • @ViktorAhlström - I just fixed it again - it was one too few.
    – mikeserv
    Jun 18 '15 at 1:15
  • @ViktorAhlström - done now.
    – mikeserv
    Jun 18 '15 at 1:46
  • Thanks! I'm still relatively new to shell scripts and that was a really nice clear-cut explanation for me. Jun 18 '15 at 2:09
  • @ViktorAhlström - there are no bashisms in this. It will run as well - probably better - in dash. It's all standard, POSIX shell-script.
    – mikeserv
    Jun 18 '15 at 2:11
0

I would save the output using backticks, then you can format it with printf:

if [ $1 == 'A-Z' ] || [ $1 ==  'a-z' ]
then
    combinations=`echo "26^$2" | bc`
elif [ $1 == 'A-Za-z' ] || [ $1 ==  'a-zA-Z' ]
    combinations=`echo "52^$2" | bc`
# ... and so on ...
fi

printf "%'.0f possible combinations\n" $combinations

As far as cleaning up your code, it's a little harder without knowing how you are using the parameters later, but one thing I would change is checking the pieces you are expecting one at a time instead of looking for all possible permutations.

#!/bin/bash

chars='0'
if  [[ $1 == *"0-9"* ]]; then
    chars="$chars+10"
fi
if  [[ $1 == *"a-z"* ]]; then
    chars="$chars+26"
fi
if  [[ $1 == *"A-Z"* ]]; then
    chars="$chars+26"
fi

combinations=`echo "($chars)^$2" | bc`
printf "%'.0f possible combinations\n" $combinations

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.