22

So I want to do generate all possible combinations of lower and upper case characters and numbers that can make up a 5 character string.

Possibilities: a..z, A..Z and 0..9.

Is there any elegant way of doing this in bash at all?

6
  • 3
    Do you want to limit yourself to ASCII or the latin script? What about diacritics like accents (é, â...)? Commented May 18, 2015 at 12:24
  • Thanks for the follow up. Updated original post for clarification.
    – ardevd
    Commented May 18, 2015 at 12:25
  • Does it really need to be in bash? Will a language like Perl or awk do?
    – terdon
    Commented May 18, 2015 at 12:25
  • 1
    Then why not just call Perl or Python from bash? Especially with perl, it is very easy to use it as a one-liner.
    – terdon
    Commented May 18, 2015 at 12:36
  • 2
    Are you trying to learn or just want the result? In the second case there are plenty of programs that do the job like john the ripper (john) and the like, which will give you plenty of possibilities.
    – YoMismo
    Commented May 18, 2015 at 14:46

9 Answers 9

21

Here's a bash solution that takes the desired length as parameter (you'd do permute 5 in your case):

#!/bin/bash
charset=({a..z} {A..Z} {0..9})
permute(){
  (($1 == 0)) && { echo "$2"; return; }
  for char in "${charset[@]}"
  do
    permute "$((${1} - 1 ))" "$2$char"
  done
}
permute "$1"

It's painfully slow, though. Dare I recommend C? https://youtu.be/H4YRPdRXKFs?t=18s

#include <stdio.h>

const char* charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char buffer[50];

void permute(int level) {
  const char* charset_ptr = charset;
  if (level == -1){
    puts(buffer);
  } else {
    while(buffer[level] = *charset_ptr++) {
      permute(level - 1);
    }
  }
}

int main(int argc, char **argv)
{
  int length;
  sscanf(argv[1], "%d", &length); 

  //Must provide length (integer < sizeof(buffer)==50) as first arg;
  //It will crash and burn otherwise  

  buffer[length] = '\0';
  permute(length - 1);
  return 0;
}

Run it:

make CFLAGS=-O3 permute && time ./permute 5 >/dev/null #about 20s on my PC

High-level languages suck at brute-forcing (which is basically what you're doing).

4
  • @Stéphane Chazelas Thank you very much for that edit. I was writing dirty, ignoring "proper" quoting as it's not needed in this case, but I am very grateful for the shortcuts! Commented May 18, 2015 at 13:51
  • I tried your bash solution. It's very nice; I like it a lot. It worked well for about 24 hours or so before I noticed my system was completely locked up. Tried something similar with `python; with a similar result, although it was considerably quicker.
    – voices
    Commented Oct 4, 2016 at 20:57
  • How to do the C solution with multi-threading?
    – dtrunk
    Commented Jul 27, 2022 at 13:50
  • 1
    @dtrunk That's an interesting modification to the problem. You can use local buffers with a reentrant permute function, partition on the first written letter and do the rest with the permute function. You'll also want to use thread-local io buffers. I even get a slowdown in the multithreaded solution if I simply use puts, but with thread-local io buffers, the multithreaded approach is faster. pastebin.com/Pz5ivKE9 Commented Jul 27, 2022 at 16:17
14

In bash, you could try:

printf "%s\n" {{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}

but that would take forever and use-up all your memory. Best would be to use another tool like perl:

perl -le '@c = ("A".."Z","a".."z",0..9);
          for $a (@c){for $b(@c){for $c(@c){for $d(@c){for $e(@c){
            print "$a$b$c$d$e"}}}}}'

Beware that's 6 x 625 bytes, so 5,496,796,992.

You can do that same loop in bash, but bash being the slowest shell in the west, that's going to take hours:

export LC_ALL=C # seems to improve performance by about 10%
shopt -s xpg_echo # 2% gain (against my expectations)
set {a..z} {A..Z} {0..9}
for a do for b do for c do for d do for e do
  echo "$a$b$c$d$e"
done; done; done; done; done

(on my system, that outputs at 700 kiB/s as opposed to 20MiB/s with the perl equivalent).

2
  • I also feel that it would add to the answer if you were to add a way to output it to a file; maybe as it's being generated so it doesn't destroy your RAM, or after it's all cached in ram
    – Hellreaver
    Commented Oct 23, 2015 at 5:07
  • 3
    @Hellreaver, they all write to a file (to stdout, whatever file that is open to; if run in a terminal, a device file like /dev/pts/something; and you can change that with shell redirection operator), not memory, but the first one builds the whole output in memory before outputting it (to the file open on stdout). Commented Oct 23, 2015 at 6:09
9

You can use crunch (which is available at least on Kali distributions).

crunch 5 5 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
8

Here's a way to do it purely in bash without having to chomp 5 GB of memory:

for c1 in {A..Z} {a..z} {0..9}
do
    for c2 in {A..Z} {a..z} {0..9}
    do
        for c3 in {A..Z} {a..z} {0..9}
        do
            for c4 in {A..Z} {a..z} {0..9}
            do
                for c5 in {A..Z} {a..z} {0..9}
                do
                    printf "%s\n" "$c1$c2$c3$c4$c5"
                done
            done
        done
    done
done
1
  • Cute. Take my upvote. Commented Dec 31, 2022 at 14:25
4

Gnu Parallel can do combinations see https://www.gnu.org/software/parallel/ Something like this:

parallel echo ::: {a..z} {A..Z} {0..9} ::: {a..z} {A..Z} {0..9} ::: {a..z} {A..Z} {0..9} ::: {a..z} {A..Z} {0..9} ::: {a..z} {A..Z} {0..9}
2

This bash version is still not as fast as Perl but it's about four times as fast as five nested loops:

printf -vtwo "%s " {{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}
for three in {{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}{{a..z},{A..Z},{0..9}}; do
    printf "$three%s\n" $two;
done
1

Well ... elegant?, yes (just a fast sample):

eval echo $(printf "%s" '{{a..z},{A..Z},{0..9}}'{,,} )

This full expression most likely will block your computer:

eval echo $(printf "%s" '{{a..z},{A..Z},{0..9}}'{,,,,} )

One non-blocking option is to use several loops:

nl=$'\n'; tab=$'\t'
n=${1:-3}
eval set -- "$2"

eval "varnames=($(echo {a..z}))"

for i in "${varnames[@]:0:$n}"; do
    header+='for '"$i"' do '
    middle+='$'"$i"
    traile+="done; "
done

loop="${header}${nl}    printf %s \"$middle\";${nl}$traile"
#echo "$loop"
eval "$loop"

Call it like:

./script 3 '{a..z} {A..Z} {0..9}'

Where the first argument is the number of characters and the second is the list (space separated) of characters used.

That will build a variable (loop) with the script to run and the last eval will execute that script. For example for:

$ ./script 5 '{a..z} {A..Z} {0..9}'

The value of loop will be:

for a do for b do for c do for d do for e do
    echo "$a$b$c$d$e";
done; done; done; done; done;
0

Easy to extend to more chars, and does not require compilation:

perl -e 'sub p { if(not @_) { print @p,"\n"; } else { $a=shift; for (@$a) { push @p,$_; p(@_); pop @p; } }} $b=["a".."z","A".."Z",0..9]; p($b,$b,$b,$b,$b)'
-3
 #!/bin/bash
 eval `for i in $(seq $1) ; do
 echo "for c$i in {a..z} {A..Z} {0..9} ; do"
 done
 echo -n "echo "
 for i in $(seq $1) ; do
 echo -n '$c'$i
 done
 echo ' ;'
 for i in $(seq $1) ; do
 echo 'done ;'
 done`

./npermuter  <some number>

You must log in to answer this question.

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