Random string generator in bash script does not count the number of given characters
I am trying to create a random character generator in a bash script in osx 10.8.5. The goal is to generate random character strings for the script generating salts for the wordpress file wp-config.php
. The snippet looks like this:
#!/bin/bash -e
read -p "Number of digits: " digits
function rand_char {
take=$(($RANDOM % 88)); i=0; echo {a..z} {A..Z} {0..9} \, \; \. \: \- \_ \# \* \+ \~ \! \§ \$ \% \& \( \) \= \? \{ \[ \] \} \| \> \< | while read -d\ char;
do
[ "$i" = "$take" ] && echo "$char\c";
((i++));
done
}
function rand_string {
c=$1;
while [ $c -gt 0 ];
do char="${char}"$(rand_char);
let c=$c-1;
done
echo $char
}
outputsalt=`rand_string $digits`
echo $outputsalt
if I enter 64 as the number of digits, the number of characters received will be different each try:
HeF6D>z}x[v=s(qRoPmNkLiIfG7E5C3A1yZwWtU§S~Q*O_M:J,b86|4]2{0)X&p (63 chars)
WtUrSpQnOkLiJ,H8F6D4B1yZ)X&V$T!R+P_M:e;c9a7>5}2{u=s(q%o§m~j#hIfG (64 chars)
_g:d,b86|4]w{u=r&p$n!lMjKhIeFcDaB>z0xYt)r&p§m~kLiJgHeFcDA1yZwX (62 chars)
}w{u=s(q%oPmNkKhIfGdEbC3A1xYvWtUrS~Q*O_L.J,H8F6|4]2?Z)X&V$n!l+j_ (64 chars)
l+j_g:e;cDaB>z}x{u=sTqRoPmNkKhIfG7E5C3A1xYvW%U§S~Q*O_L.J,b86|4] (63 chars)
Is there a way that the number of characters sticks to the specified number? Best wishes Ralph
source to share
Try a simpler case:
function rand_char {
take=$(($RANDOM % 2)); i=0; echo a b | while read -d\ char;
do
[ "$i" = "$take" ] && echo "$char\c";
((i++));
done
}
It will generate a
spaces as well, but not b
.
We can further reduce the problem to:
echo a b | while read -d\ char; do echo "$char"; done
which only writes a
, not b
. This is because you are instructing read
to read before a space and b
there is no space after , so it fails. This means that one out of every 88 characters will be flushed, which will result in your lines being slightly shorter.
The simplest solution is to add a dummy argument to create a space at the end:
echo {a..z} {A..Z} {0..9} (etc etc) \} \| \> \< '' | while read ...
# Here ---^
Note that your method simply adds 15 bits of entropy to the salt, while the absolute minimum should be 64. It is much easier and safer to do this:
LC_CTYPE=C tr -cd 'a-zA-Z0-9,;.:_#*+~!@$%&()=?{[]}|><-' < /dev/urandom | head -c 64
(note: this replaces your unicode paragraph character with ascii @)
source to share
No offense, according to your coding style, errr ... not the best I've seen :)
.
#!/bin/bash -e
read -p "Number of digits: " digits
# TODO: test that digits is really a number
chars=( {a..z} {A..Z} {0..9} \, \; \. \: \- \_ \# \* \+ \~ \! \§ \$ \% \& \( \) \= \? \{ \[ \] \} \| \> \< )
function rand_string {
local c=$1 ret=
while((c--)); do
ret+=${chars[$((RANDOM%${#chars[@]}))]}
done
printf '%s\n' "$ret"
}
outputsalt=$(rand_string $digits)
echo "$outputsalt"
source to share
I see two problems in your script.
I sure that
take=$(($RANDOM % 88));
it should be
take=$(($RANDOM % 87));
Otherwise, it looks like you are walking past the end of your input stream.
Another problem is char: \§
Bash sees this as two characters (wide char?). I will remove it from your options.
Of course, this would mean that the above line would be:
take=$(($RANDOM % 86));
Doing these two things essentially works for me.
Edited:
@, the other guy has a better answer. Adding space, not decreasing the modulus, ensures you get every character
source to share
Another way to do it, if you fancy one-liners:
perl -le 'print map { ("a".."z","A".."Z",0..9,",",";",".",":","-","_","#","*","+","~","!","§","\$","%","&","(",")","=","?","{","}","[","]","|","<",">") [rand 87] } 1..63'
or, since you probably won't need a newline:
perl -e 'print map { ("a".."z","A".."Z",0..9,",",";",".",":","-","_","#","*","+","~","!","§","\$","%","&","(",")","=","?","{","}","[","]","|","<",">") [rand 87] } 1..63'
source to share