Random Password Generator Bash

This guarantees one and only one special characters, as well as at least one of digits, lower case, and upper case. To place those characters randomly within the password, sort -R is used to scramble the order of characters before the password is printed:

#!/bin/bash
choose() { echo ${1:RANDOM%${#1}:1}; }

{
    choose '!@#$%^\&'
    choose '0123456789'
    choose 'abcdefghijklmnopqrstuvwxyz'
    choose 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    for i in $( seq 1 $(( 4 + RANDOM % 8 )) )
    do
        choose '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    done
} | sort -R | tr -d '\n'
echo ""

How it works

  1. We define a convenience function:

    choose() { echo ${1:RANDOM%${#1}:1}; }
    

    choose takes one argument, a string, and randomly selects a character from that string.

  2. We print one character per line of one of each of the required types of characters:

    choose '!@#$%^\&'
    choose '0123456789'
    choose 'abcdefghijklmnopqrstuvwxyz'
    choose 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
  3. We print the remaining number of characters needed, one character per line. These characters are selected randomly from digits, lower case, and upper case:

    for i in $( seq 1 $(( 4 + RANDOM % 8 )) )
    do
        choose '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    done
    
  4. From the above, the special character is always first and the digit second, etc. We need to randomize. We do that with sort -R:

    sort -R | tr -d '\n'
    

    tr -d '\n' serves to removes the newlines from between the characters, resulting in a string of characters on one line.

Improvement

In the comments, JonathanLeffer shows that the output of sort -R is not fully random. sort -R sorts on a hash of each line. Apparently a random seed is added but each line receives the same seed. To get around this, the version below provides its own seed on each line. This is done by a change to the choose function. At the end, awk is used to remove those numbers and display the complete password:

#!/bin/bash
choose() { echo ${1:RANDOM%${#1}:1} $RANDOM; }

{
    choose '!@#$%^\&'
    choose '0123456789'
    choose 'abcdefghijklmnopqrstuvwxyz'
    choose 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    for i in $( seq 1 $(( 4 + RANDOM % 8 )) )
    do
        choose '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    done

} | sort -R | awk '{printf "%s",$1}'
echo ""

In awk, each line is divided into fields. The first field is the randomly chosen letter while the second is the random number that we added to seed the hash. The statement printf "%s",$1 prints the first field (with no trailing whitespace) and ignores the second field. The end result is the password that we want.

Capturing the password to a variable

To capture the output to a variable, we use command substitution:

choose() { echo ${1:RANDOM%${#1}:1} $RANDOM; }
pass="$({ choose '!@#$%^\&'
  choose '0123456789'
  choose 'abcdefghijklmnopqrstuvwxyz'
  choose 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  for i in $( seq 1 $(( 4 + RANDOM % 8 )) )
     do
        choose '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
     done
 } | sort -R | awk '{printf "%s",$1}')"

Command substitution, denoted by $(...), runs the commands in the parens and captures their stdout.


pwgen is installed on some linux distros (notably Ubuntu) by default and has a windows version as well. Why not use that?

Tags:

Passwords

Bash