7

I have an array in Bash, say it contains the numbers {1, 2, 3, 4, 5}. I want to extract some of those numbers randomly, such that the same number doesn't get extracted twice.

Basically, if I wanted to extract 3 numbers from the array, I want results like: {3, 4, 1} or {5, 2, 4} and not {1, 1, 3} or {2, 5, 2}.

I've tried deleting elements as I extract them, but it always seems to mess up. Can anyone help?

1

5 Answers 5

14

Decided to write an answer, as I found the --input-range option to shuf that turned out handy:

N=3
ARRAY=( zero one two three four five )

for index in $(shuf --input-range=0-$(( ${#ARRAY[*]} - 1 )) -n ${N})
do
    echo ${ARRAY[$index]}
done
Sign up to request clarification or add additional context in comments.

Comments

2

How about this:

for i in {1..10}; do
    echo $i
done | shuf 

That will return all the numbers. If you only want a specific amount, do this:

numbers=5    
for i in {1..10}; do
    echo $i
done | shuf | head -$numbers 

And if you want to change the numbers, just change the {1..10} variable to whatever you want.

1 Comment

Your code absolutely works, but if you do shuf --input-range 1-10 you don't need the for loop to render a set of array indices.
1

Yet another syntax, still using shuf, and preserving elements with spaces:

N=3
ARRAY=( one "two = 2" "3 is three" 4 five )


for el in "${ARRAY[@]}"; do echo $el; done | shuf | head -$N

Comments

1

very simple 'oneliner' i like:

shuf -e ${POOL[@]} -n3

will give you 3 random elements from your $POOL array

ex:

#~ POOL=(a b c d e)
#~ shuf -e ${POOL[@]} -n3

d
e
a

Comments

0

This might work for you, if you want a genuine pure bash solution.

takeNrandom() {
# This function takes n+1 parameters: k a1 a2 ... an
# Where k in 0..n
# This function sets the global variable _takeNrandom_out as an array that
# consists of k elements chosen at random among a1 a2 ... an with no repetition
    local k=$1 i
    _takeNrandom_out=()
    shift
    while((k-->0 && $#)); do
        ((i=RANDOM%$#+1))
        _takeNrandom_out+=( "${!i}" )
        set -- "${@:1:i-1}" "${@:i+1}"
    done
}

Try it:

$ array=( $'a field with\na newline' 'a second field' 'and a third one' 42 )
$ takeNrandom 2 "${array[@]}"
$ declare -p _takeNrandom_out
declare -a _takeNrandom_out='([0]="a second field" [1]="a field with
a newline" [2]="and a third one")'

(the newline is really preserved in the array field).

This uses positional parameters, and uses set -- "${@:1:i-1}" "${@:i+1}" to remove the i-th positional parameter. We also used indirect expansion in the line _takeNrandom_out+=( "${!i}" ) to have access to the i-th positional parameter.

Note. This uses the RANDOM variable with a modulo, so the distribution is not exactly uniform. It should be fine for arrays with a small number of fields. Anyway if you have a huge array, you probably shouldn't be using Bash in the first place!

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.