I'm trying to generate an eight character alphanumeric string using POSIX compliant shell tools. I'm trying to use /dev/urandom to produce this string (incidentally I did not know that /dev/random and /dev/urandom are not specified by POSIX, but I'm going with it because they are present on Linux, FreeBSD, Mac OS, AIX etc.)
There are a plethora of guides that show how this can be done, but none that I've found are close to being posixly correct. In particular I keep seeing the use of head -c (the -c argument is not defined in POSIX) like below:
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo ''
Another concern I have is that almost none of the solutions I have seen respect the difference between a byte stream and a character stream, which makes me worried that I'm producing strings that are unsafe.
This is the best I can seem to come up with, but I don't fully understand it:
strings -n 1 < /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1
It's POSIX compliant (minus the /dev/urandom) but have I achieved my end correctly? If so, is there a better way to achive this end? It would also be cool if there was a way to produce a random string without /dev/urandom, but I think I'm dreaming on that one.
Just use awk
, which POSIX mandates provide a rand
function.
$ cat password.awk
BEGIN {
srand();
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
s = "";
for(i=0;i<8;i++) {
s = s "" substr(chars, int(rand()*62), 1);
}
print s
}
$ awk -f password.awk
Cl7A4KVx
(This can almost certainly be code-golfed into something shorter; my awk
skills are somewhat limited.)
If you need multiple passwords, pass the number needed as an argument so that you don't need to run awk
more than once per second:
BEGIN {
srand();
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for(i=0;i<n;i++) {
s = "";
for(j=0;j<8;j++) {
s = s "" substr(chars, int(rand()*62), 1);
}
print s
}
}
To pass a value of n
, run as awk -v n=5 -f password.awk
to generate 5 passwords.
Alternatively, you can pass a different seed directly to awk
:
BEGIN {
srand(seed);
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
s = "";
for(i=0;i<8;i++) {
s = s "" substr(chars, int(rand()*62), 1);
}
print s
}
with awk -v seed=$newseed -f password.akw
. Note that newseed
itself needs to have a different value each time, but using a simple sequential sequence of seeds should be sufficient.
seed=$(date +%s)
awk -v seed=$seed -f password.awk; seed=$((seed + 1))
awk -v seed=$seed -f password.awk; seed=$((seed + 1))
awk -v seed=$seed -f password.awk; seed=$((seed + 1))
# etc