29

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?

10 Answers 10

28

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
Sign up to request clarification or add additional context in comments.

8 Comments

thanks, but i don't understand the use of the module %, can you or someone explain me why?
@Dimareal: $RANDOM could be too large. By using modulo, the result can't be greater than the length of the $chars, i.e. you can use it to index a random char.
Is there a version that works with "ash" and "sh" and not only with bash? I'm working on embedded system running OpenWrt and because or space restraints there is no space for bash. I get this error: # /tmp/rnd.sh /tmp/rnd.sh: line 5: syntax error: bad substitution
if chars contains escape characters, this can launch the bash command l. Try it with chars='<=>| -,;:!/."()[]{}*\&#%+012345689abcdefghiklmnopqrstuvwxyz'
@MrMartin: No, it doesn't run any command, it just expands a glob. Fixed by double quoting.
|
25

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
}

2 Comments

tr: Illegal byte sequence
@Sukima please try to add LC_ALL=C before the command and let me know if it works or not
14

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

Comments

13

Another way to generate a 32 bytes (for example) hexadecimal string:

xxd -l 32 -c 32 -p < /dev/random

add -u if you want uppercase characters instead.

Comments

8

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

1 Comment

I would like to add that when using using a Makefile, tr from /dev/random will cause tr: write error: Broken pipe errors which this solution avoids. Well done.
5
head -1 <(fold -w 20  <(tr -dc 'a-zA-Z0-9' < /dev/urandom))

This is safe to use in bash script if you have safety options turned on:

set -eou pipefail

This is a workaround of bash exit status 141 when you use pipes

tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 20 | head -1

Comments

3

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

Comments

3

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)

4 Comments

Good one! Better to use it in dry-run mode so we do not actually create anything. Simply $(mktemp --dry-run XXXXXXXX)
Oh, I didn't notice that. Why is there it is unsafe? -u, --dry-run do not create anything; merely print a name (unsafe) I'll edit my answer to add that variant, thank you Bazi!
Not that it matters a whole lot but the dry-run flag is a gnu extension and does not exist on BSDs.
@dosmanak - a bit of a late reply, but if you are still wondering: there is no guarantee that two calls to 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).
2

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)

Comments

0

Doesn't use $RANDOM but if you have /dev/random you could do this and it's compatible with bash and other shells such as plain Bourne:

pwd=$(dd status=noxfer if=/dev/random bs=1 count=7 2>/dev/null | hexdump -C | grep -E -o '[a-z0-9A-Z]' | tr -d '\n' | tr -d '0')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.