- Article pages now have a discussion option at the bottom (moderated/captcha, but no registration needed)

This is an old revision of the document!


Print a random string or select random elements

snipplet:
terminal, line
LastUpdate:
2011-10-31
Contributor:
Dan Douglas (ormaaj), Riviera
type:
snipplet

It should go without saying that the methods presented here are for educational purposes, and borderline toys - not intended as examples of good style. There are more reasonable, readable ways of achieving these effects in production code.

This one's a very close variation on Print a horizontal line (credit goes to those guys). You may wish to read it first, as well as The printf command. We're basically exploiting the same printf field width specifier concept to truncate the values of a sequence expansion.

This example prints a string of ten alphanumeric characters by selecting random elements from the array "a". Good for creating random passwords.

a=( {a..z} {A..Z} {0..9} )
printf '%.1s' "${a[RANDOM%${#a[@]}]}"{0..9} $'\n'

The added bit of cleverness that makes this work is to notice that brace expansion occurs before parameter expansion. First the sequence expansion generates ten parameters, then the parameters are expanded left-to-right causing the arithmetic for each to be evaluated individually, resulting in independent selection of random element. To get ten of the same element, put the array selection inside the format where it will only be evaluated once, just like the dashed-line version:

printf "%.s${a[RANDOM%${#a[@]}]}" {0..9} 

Like the dashed-line trick, the number of elements printed is of course going to be static unless some eval trickery is used to generate the code for the brace expansion first. Unlike the dashed-line, the stuff we want to keep isn't a literal part of the format string. Consequently, a fixed width must be used for every string element of the array so that the field width can be set, which in this case is a single character. But what if we wanted to randomly select elements whose length isn't constant?

a=( 'one' 'two' 'three' 'four' 'five' 'six' 'seven' 'eight' 'nine' 'ten' )
printf '%.*s ' $(echo "${#a[x=RANDOM%${#a[@]}]} ${a[x]}"{1..10})

This generates each parameter and it's length in pairs. The '*' modifier instructs printf to use the value preceding each parameter as the field width. Note the space between the parameters. This example unfortunately relies upon the unquoted command substitution to perform unsafe wordsplitting so that printf gets each argument. Values in the array can't contain spaces or anything which might be interpreted as a pattern, and will be subject to pathname expansion without first disabling it with set -f. Thanks to Riviera in #bash for pointing out a clever albeit awkward solution also mentioned in Brace expansion

a=(one two three)
echo "${a[$((RANDOM%${#a[@]}))]}"{,}{,,,,}

…And bonus points for a method whose optimal solution requires finding prime factors.

Another, but somewhat less awesome, way of doing this is by dividing RANDOM by its max value and multiplying the resulting fraction with the length of the list to give you an index for the array:

rand() {
	printf $((  $1 *  RANDOM  / 32767   ))
}
rand_element () {
    local -a th=("$@")
    unset th[0]
    printf $'%s\n' "${th[$(($(rand "${#th[*]}")+1))]}"
}

rand_element monkey horse bird cow

# (submitted to https://raw.github.com/dalhuijsen/bashnative/master/function/rand)

Discussion

Enter your comment
 
print_a_random_string_or_select_random_elements.1328992471.txt · Last modified: 2012/02/11 21:34 by drkrimson
GNU Free Documentation License 1.3
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0