I am trying to use the Bash variable $RANDOM to create a random string that consists of 8 characters from a variable that contains integer and alphanumeric digits, e.g., var="abcd1234ABCD".
How can I do that?
Use parameter expansion. ${#chars} is the number of possible characters, % is the modulo operator. ${chars:offset:length} selects the character(s) at position offset, i.e. 0 - length($chars) in our case.
chars=abcd1234ABCD
for i in {1..8} ; do
echo -n "${chars:RANDOM%${#chars}:1}"
done
echo
chars contains escape characters, this can launch the bash command l. Try it with chars='<=>| -,;:!/."()[]{}*\&#%+012345689abcdefghiklmnopqrstuvwxyz'For those looking for a random alpha-numeric string in bash:
LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 64
The same as a well-documented function:
function rand-str {
# Return random alpha-numeric string of given LENGTH
#
# Usage: VALUE=$(rand-str $LENGTH)
# or: VALUE=$(rand-str)
local DEFAULT_LENGTH=64
local LENGTH=${1:-$DEFAULT_LENGTH}
LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c $LENGTH
# LC_ALL=C: required for Mac OS X - https://unix.stackexchange.com/a/363194/403075
# -dc: delete complementary set == delete all except given set
}
OPTION 1 - Specific length, only lowercase letters, numbers and dashes
cat /proc/sys/kernel/random/uuid OR on macOS just use uuidgen
After generating 1 billion UUIDs every second for the next 100 years, the probability of creating just one duplicate would be about 50%. The probability of one duplicate would be about 50% if every person on earth owns 600 million UUIDs 😇 source
DEMO: x=100; while [ $x -gt 0 ]; do cat /proc/sys/kernel/random/uuid; x=$(($x-1)); done
Examples:
be821d3c-c484-41d9-8fc8-eb8bc3683360
ba0b72e2-7969-481e-94c2-7a716c40c3e8
e0183127-a6e3-453b-87cd-7ea912afc49a
0ad84c92-186a-4c4d-aea9-5951083c9ed8
a42e0c6a-4f06-4c32-a289-dd0391f11aba
a82b27d4-9bdb-403d-add4-8a72d36fb864
a16fcd29-02cd-448a-a048-ae6bde3c707c
OPTION 2 - No specific length, no openssl needed, only letters and numbers, slower than option 3
sed "s/[^a-zA-Z0-9]//g" <<< $(cat /dev/urandom | tr -dc 'a-zA-Z0-9!@#$%*()-+' | fold -w 32 | head -n 1)
OR on macOS:
sed "s/[^a-zA-Z0-9]//g" <<< $(head -c 24 /dev/urandom | base64 | tr -dc '[:print:]' | tr -dc 'a-zA-Z0-9!@#$%*()-+' | fold -w 32 | head -n 1)
DEMO: x=100; while [ $x -gt 0 ]; do sed "s/[^a-zA-Z0-9]//g" <<< $(cat /dev/urandom | tr -dc 'a-zA-Z0-9!@#$%*()-+' | fold -w 32 | head -n 1) <<< $(openssl rand -base64 17); x=$(($x-1)); done
Examples:
j0PYAlRI1r8zIoOSyBhh9MTtrhcI6d
nrCaiO35BWWQvHE66PjMLGVJPkZ6GBK
0WUHqiXgxLq0V0mBw2d7uafhZt2s
c1KyNeznHltcRrudYpLtDZIc1
edIUBRfttFHVM6Ru7h73StzDnG
OPTION 3 - No specific length, openssl needed, only letters and numbers (if used with sed), faster than option 2
openssl rand -base64 12 # only returns
rand=$(openssl rand -base64 12) # only saves to var
sed "s/[^a-zA-Z0-9]//g" <<< $(openssl rand -base64 17) # leave only letters and numbers
# The last command can go to a var too.
DEMO: x=100; while [ $x -gt 0 ]; do sed "s/[^a-zA-Z0-9]//g" <<< $(openssl rand -base64 17); x=$(($x-1)); done
Examples:
9FbVwZZRQeZSARCH
9f8869EVaUS2jA7Y
V5TJ541atfSQQwNI
V7tgXaVzmBhciXxS
Not using $RANDOM, but worth mentioning.
Using shuf as source of entropy (a.k.a randomness) (which, in turn, may use /dev/random as source of entropy. As in `shuf -i1-10 --random-source=/dev/urandom) seems like a solution that use less resources:
$ shuf -er -n8 {A..Z} {a..z} {0..9} | paste -sd ""
tf8ZDZ4U
tr from /dev/random will cause tr: write error: Broken pipe errors which this solution avoids. Well done.Using sparse array to shuffle characters.
#!/bin/bash
array=()
for i in {a..z} {A..Z} {0..9}; do
array[$RANDOM]=$i
done
printf %s ${array[@]::8} $'\n'
(Or alot of random strings)
#!/bin/bash
b=()
while ((${#b[@]} <= 32768)); do
a=(); for i in {a..z} {A..Z} {0..9}; do a[$RANDOM]=$i; done; b+=(${a[@]})
done
tr -d ' ' <<< ${b[@]} | fold -w 8 | head -n 4096
Little bit obscure but short to write solution is
RANDSTR=$(mktemp XXXXX) && rm "$RANDSTR"
expecting you have write access to current directory ;-)
mktemp is part of coreutils
UPDATE: As Bazi pointed out in the comment, mktemp can be used without creating the file ;-) so the command can be even shorter.
RANDSTR=$(mktemp --dry-run XXXXX)
$(mktemp --dry-run XXXXXXXX)-u, --dry-run do not create anything; merely print a name (unsafe) I'll edit my answer to add that variant, thank you Bazi!mktmp with --dry-run will not produce the same string, or that a subsequent call without --dry-run will duplicate one of the earlier strings. So if you use --dry-run to pick filenames and later use them you could have conflicts. Without --dry-run, mktemp can check the filesystem for any conflict and regenerate as needed. Of course, you can still use values returned without -dry-run unsafely (recreating the file mktemp created after it has been deleted, for instance).An abbreviated safe pipe workaround based on Radu Gabriel's answer and tested with GNU bash version 4.4.20 and set -euxo pipefail:
head -c 20 <(tr -dc [:alnum:] < /dev/urandom)