Bash on *nix (109)
while ! grep -Pq [A-Z].*[a-z].*[0-9].*[\\W_]<<<$a$a$a$a
do a=`tr -dc !-~</dev/urandom|head -c15`
done
echo $a
To work correctly, $a
must not be set to a valid but non-random password up front. If you want to include a=
and a line break up front, that's three more characters but it allows you to run the thing repeatedly. You can obviously also replace all newlines with ;
so you have a one-liner which you can execute as often as you whish.
Furthermore, you should have set LC_ALL=C
or not set any locale-specific environment variables (LANG
and LC_CTYPE
in particular), since the character ranges depend on collation order being equal to ascii order.
/dev/urandom
is the a source of random bytes. !-~
is the range of all permissible characters, as specified in the question. tr -dc
removes all characters not listed in its next argument. head
takes 15 of the remaining characters. grep
checks whether each of the required kinds does occur at least once. Its input consists of four copies of the candidate, so order of the symbols does not matter, hence all possible passwords stand a chance of getting selected. The -q
to grep suppresses output.
For reasons unknown, /dev/random
instead of /dev/urandom
takes ages. It seems like entropy got exhausted pretty quickly. If you cd
into /dev
, you can avoid some more bytes, but that feels a bit like cheating.
Python 2 (138)
import re,random
a=''
while not re.search('[A-Z].*[a-z].*[0-9].*[\W_]',a*4):
a=''.join(random.sample(map(chr,range(33,127))*15,15))
print a
To make the code readable I added a newline and indentation after the loop which is not neccessary and which I did not count.
This is essentially the same idea as in the bash version. The random source here is random.sample
, which will not repeat elements. To counter this fact, we use 15 copies of the list of permissible letters. That way, every combination can still occur, although those with repeated letters will occur less often. But I decide to consider this a feature, not a bug, since the question did not require equal probability for all permutations, only the possibility.
Python 3 (145)
import re,random
a=''
while not re.search('[A-Z].*[a-z].*[0-9].*[\W_]',a*4):
a=''.join(random.sample(list(map(chr,range(33,127)))*15,15))
print(a)
One newline and one indent again not counted. Apart from some Python-3-specific syntax overhead this is the same solution as for Python 2.
JavaScript (161)
a=[];for(i=33;i<127;)a.push(s=String.fromCharCode(i++));
while(!/[A-Z].*[a-z].*[0-9].*[\W_]/.test(s+s+s+s))
for(i=0,s="";i<15;++i)s+=a[Math.random()*94|0];alert(s)
I added the newlines for readability, but did not count them.
R (114)
s<-""
while(!grepl("[A-Z].*[a-z].*[0-9].*(\\W|_)",paste(rep(s,4),collapse="")))
s<-intToUtf8(sample(33:126,15,T))
s
Linebreak and indentation inside loop added but not counted. If you feel like it, you can again move this to a single ;
-separated line.
correcthorsebatterystaple
\$\endgroup\$