The default character set excludes easy to confuse characters ILOl0. It is fast too
Generating 1 million 40 character passwords
time pw -n 1000000 >/dev/null
0.47s user 0.24s system 229% cpu 0.310 total
cat pw
#!/usr/bin/env bash
#set -x
num_passwords=20 # Default number of passwords to return.
pw_len=40 # Default password length.
random_data='/dev/urandom' # Random data
urandom_bytes_default=300000 # Default random bytes to read.
letters='A-HJ-KM-NP-Za-km-z' # Default letters set.
numbers='1-9' # Default numbers set.
symbols='!?*^_@#%^&*()=+<>}{][;:",./|~\\'\''`-' # Default symbols set. If dash "-" is needed, put it at the end
characters="$letters$numbers$symbols" # All default sets combined
min_calculated_urandom_bytes=20000 # Minimum bytes when calculated. Fix issue when not enough data for simple character sets
urandom_bytes_user=0 # Leave at 0, for use with logic of -b , --bytes=
urandom_bytes_calculated=0 # Leave at 0, for use with end logic
regex_match_flags="^-(b|-bytes=|c|-characters=|l|-length=)$" # Pattern to check against a flag being blank and reading next flag as arguemnt
while test $# -gt 0; do
case "$1" in
-h|--help)
echo " "
echo " "
echo " "
echo "pw - generate passwords"
echo " "
echo "pw [options]"
echo " "
echo "options:"
echo "-b NUM , --bytes=NUM Specify bytes to read from "$random_data". Not compatible with flag -n, --ncount. Defaults to $urandom_bytes_default bytes"
echo "-c 'CHAR', --characters='CHAR' Specify allowed password characters. Defaults to '$characters'"
echo "-h , --help Show brief help"
echo "-l NUM , --length=NUM Specify password length. Defaults to length of $pw_len"
echo "-n NUM , --ncount=NUM Specify number of passwords to return. Not compatible with flag -b, --bytes"
echo " "
echo " "
echo " "
echo " "
echo "examples:"
echo " "
echo " "
echo "# 20 character alphanumeric with symbols "'!?"*#-'" using 20000 bytes of data from "$random_data""
echo "pw --bytes=20000 --characters='a-zA-Z0-9"'!?"*#-'"' --length=20"
echo " IjLVomO*LZIvBWhmITtS"
echo "pw -b 20000 -c 'a-zA-Z0-9"'!?"*#-'"' -l 20"
echo " IjLVomO*LZIvBWhmITtS"
echo " "
echo " "
echo " "
echo "# 200 passwords using default values"
echo "pw --ncount=200"
echo ' !=[8x|d`dHdVA-:xn8t>G=~tkgbg}T#~2(/r?9N&'
echo " ...{200 lines}"
echo " "
echo "pw -c '18bu' -l 10 -n 2"
echo " bb8b8bb1ub"
echo " 88b1ub8b8u"
echo " "
echo " "
echo "pw -c '0-4' --length=80 --ncount=10"
echo " 10132440443120133034412013333104142320411133221101130324111200442311420044122312"
echo " "
echo " "
echo "pw -c 'zplaeiou' --length=80 --ncount=1"
echo " uuzzzalilepauzuepaazoizoeiiaazupupalolzliluuoazluzuepzlozepapaioipupapleuzaolpuu"
echo " "
echo " "
echo "pw -c '1-4*-' -l 10 -n 2"
echo " 2414443*24"
echo " *123-*4-31"
echo " "
echo " "
echo "pw -b 400 -c 'a-zA-Z0-9 [#!?*(){}~[]/\\-]'\''' -l 40"
echo " EVuMxtVR**6}?M2HTZlED{ARjKL?D]r8h[7Pidvo"
echo " "
echo " "
echo " "
exit 0
;;
-b)
shift
# Test that -b value (previously shifted $1) is gt 0 before setting var urandom_bytes_user
# And test that $pw_line_count_target has not been set
if [[ $1 -gt 0 ]] && [[ -z $pw_line_count_target ]] 2> /dev/null; then
urandom_bytes_user=$1
urandom_bytes_default=0
pw_line_count_target=0
else
printf "error: \"-b NUM\" needs numeral greater that 0. Value > 1000 recommended\n"
exit 1
fi
shift
;;
--bytes*)
# Test that --bytes value "${1/*"="/}" is gt 0 before setting var urandom_bytes_user
# And test that pw_line_count_target is not set
if [[ "${1/*"="/}" -gt 0 ]] && [[ $pw_line_count_target -le 0 ]] 2> /dev/null; then
urandom_bytes_user="${1/*"="/}"
urandom_bytes_default=0
pw_line_count_target=0
else
if [[ ! $pw_line_count_target -le 0 ]] 2> /dev/null; then
printf "\nflag -n, --ncount not compatible with flag -b, --bytes\n"
exit 1
else
printf "error: usage \"--bytes=NUM\" needs numeral greater that 0. Value > 1000 recommended\n"
exit 1
fi
fi
shift
;;
-c)
shift
# Before set var characters, test for -c value (previously shifted $1) being blank,
# or another flag shifted in as unintended -c value.
if [[ ! -z $1 ]] && [[ ! "$1" =~ $regex_match_flags ]]; then
characters="$1"
else
printf "error: usage \"-c 'CHARACTERS'\" (allowed password characters) needs value\n"
exit 1
fi
shift
;;
--characters*)
# Before set var characters, test for --characters string "${1/*"="/}" being blank,
# or another flag shifted in as unintended --characters string by checking
# $characters_to_check for regex match on $regex_match_flags.
characters_to_check="${1/*"="/}"
if [[ ! -z "${1/*"="/}" ]] && [[ ! "$characters_to_check" =~ $regex_match_flags ]]; then
characters="${1/*"="/}"
else
printf "error: usage \"--characters 'CHARACTERS'\" (allowed password characters) needs value\n"
exit 1
fi
shift
;;
-l)
shift
# Test that -l value (previously shifted $1) is gt 0 before setting var pw_len
if [ $1 -gt 0 ] 2> /dev/null; then
pw_len=$1
else
printf "error: usage \"-l NUM\" (password length) needs numeral greater that 0\n"
exit 1
fi
shift
;;
--length*)
# Test that --length value "${1/*"="/}" is gt 0 before setting var pw_len
if [[ "${1/*"="/}" -gt 0 ]] 2> /dev/null; then
pw_len="${1/*"="/}"
else
printf "error: usage \"--length=NUM\" (password length) needs numeral greater that 0\n"
exit 1
fi
shift
;;
-n)
shift
# Test that -b value (previously shifted $1) is gt 0 before setting var pw_line_count_target
if [ $1 -gt 0 ] ; then
pw_line_count_target=$1
urandom_bytes_default=0
else
printf "error: \"-n NUM\" needs numeral greater that 0\n"
exit 1
fi
shift
;;
--ncount*)
# Test that --bytes value "${1/*"="/}" is gt 0 before setting var pw_line_count_target
if [[ "${1/*"="/}" -gt 0 ]] ; then
pw_line_count_target="${1/*"="/}"
urandom_bytes_default=0
else
printf "error: usage \"--ncount=NUM\" needs numeral greater that 0\n"
exit 1
fi
shift
;;
*)
break
;;
esac
done
# Test that urandom_bytes_user has not been changed from 0
# And test that pw_line_count_target gt 0
if [[ $pw_line_count_target -gt 0 ]] && [[ $urandom_bytes_user -eq 0 ]] ; then
count_out_of_10000="$(head -c 10000 < "$random_data" | tr -dc "$characters" | wc -c)"
urandom_bytes_calculated=$(( (13000/$count_out_of_10000) * ($pw_len * $pw_line_count_target) ))
if [[ $urandom_bytes_calculated -lt $min_calculated_urandom_bytes ]] ; then
urandom_bytes_calculated=$min_calculated_urandom_bytes
fi
else
if [[ $pw_line_count_target -gt 0 ]] && [[ $urandom_bytes_user -ne 0 ]] ; then
printf "\nflag \" -n|--ncount \" not compatible with flag \" -b|--bytes \"\n"
exit 1
fi
fi
if [[ $pw_line_count_target -eq 0 ]]; then
pw_line_count_target=$num_passwords
fi
# PW generation bits
urandom_bytes=$(( ($urandom_bytes_default) + ($urandom_bytes_user) + ($urandom_bytes_calculated) ))
head -c "$urandom_bytes" < "$random_data" |
tr -dc "$characters" |
fold -s -w$pw_len |
head -n "$pw_line_count_target"