问题
Given an array of elements (servers), how do I shuffle the array to obtain a random new array ?
inarray=("serverA" "serverB" "serverC")
outarray=($(randomize_func ${inarray[@]})
echo ${outarray[@]}
serverB serverC serverA
There is a command shuf
(man page) but it does not exist on every linux.
This is my first attempt to post a self-answered question stackoverflow, if you have a better solution, please post it.
回答1:
This is another pure Bash solution:
#! /bin/bash
# Randomly permute the arguments and put them in array 'outarray'
function perm
{
outarray=( "$@" )
# The algorithm used is the Fisher-Yates Shuffle
# (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle),
# also known as the Knuth Shuffle.
# Loop down through 'outarray', swapping the item at the current index
# with a random item chosen from the array up to (and including) that
# index
local idx rand_idx tmp
for ((idx=$#-1; idx>0 ; idx--)) ; do
rand_idx=$(( RANDOM % (idx+1) ))
# Swap if the randomly chosen item is not the current item
if (( rand_idx != idx )) ; then
tmp=${outarray[idx]}
outarray[idx]=${outarray[rand_idx]}
outarray[rand_idx]=$tmp
fi
done
}
inarray=( 'server A' 'server B' 'server C' )
# Declare 'outarray' for use by 'perm'
declare -a outarray
perm "${inarray[@]}"
# Display the contents of 'outarray'
declare -p outarray
It's Shellcheck-clean, and tested with Bash 3 and Bash 4.
The caller gets the results from outarray
rather than putting them in outarray
because outarray=( $(perm ...) )
doesn't work if any of the items to be shuffled contain whitespace characters, and it may also break if items contain glob metacharacters. There is no nice way to return non-trivial values from Bash functions.
If perm
is called from another function then declaring outarray
in the caller (e.g. with local -a outarray
) will avoid creating (or clobbering) a global variable.
The code could safely be simplified by doing the swap unconditionally, at the cost of doing some pointless swaps of items with themselves.
回答2:
This is the solution I found (it even works in bash < 4.0).
Shellchecked and edited thanks to comments below.
#!/bin/bash
# random permutation of input
perm() {
# make the input an array
local -a items=( "$@" )
# all the indices of the array
local -a items_arr=( "${!items[@]}" )
# create out array
local -a items_out=()
# loop while there is at least one index
while [ ${#items_arr[@]} -gt 0 ]; do
# pick a random number between 1 and the length of the indices array
local rand=$(( RANDOM % ${#items_arr[@]} ))
# get the item index from the array of indices
local items_idx=${items_arr[$rand]}
# append that item to the out array
items_out+=("${items[$items_idx]}")
### NOTE array is not reindexed when pop'ing, so we redo an array of
### index at each iteration
# pop the item
unset "items[$items_idx]"
# recreate the array
items_arr=( "${!items[@]}" )
done
echo "${items_out[@]}"
}
perm "server1" "server2" "server3" "server4" "server4" "server5" "server6" "server7" "server8"
It is more than possible that it can be optimized.
回答3:
The sort
utility has the ability to shuffle lists randomly.
Try this instead:
servers="serverA serverB serverC serverD"
for s in $servers ; do echo $s ; done | sort -R
回答4:
You should use shuf
:
inarray=("serverA" "serverB" "serverC")
IFS=$'\n' outarray=($(printf "%s$IFS" "${inarray[@]}" | shuf))
Or when using array members with newlines and other strange characters, use null delimetered strings:
inarray=("serverA" "serverB" "serverC")
readarray -d '' outarray < <(printf "%s\0" "${inarray[@]}" | shuf -z)
来源:https://stackoverflow.com/questions/53229380/how-to-randomly-loop-over-an-array-shuffle-in-bash