【问题标题】:how to randomly loop over an array (shuffle) in bash [duplicate]如何在bash中随机循环数组(随机播放)[重复]
【发布时间】:2018-11-09 16:11:24
【问题描述】:

给定一个元素数组(服务器),我如何打乱数组以获得一个随机的新数组?

inarray=("serverA" "serverB" "serverC")

outarray=($(randomize_func ${inarray[@]})

echo ${outarray[@]}
serverB serverC serverA

有一个命令shuf (man page),但不是每个 linux 上都存在。

这是我第一次尝试发布一个自我回答的问题 stackoverflow,如果您有更好的解决方案,请发布。

【问题讨论】:

  • inarrayoutarray 实际上都不是数组。
  • 而且这两个赋值在语法上都是无效的。
  • 抱歉,伪代码,我编辑我的问题
  • 我质疑是否需要在脚本中对数组进行洗牌,而该脚本不适合用另一种语言编写。
  • 相信我,这里的真实用例:)

标签: arrays bash random shuffle


【解决方案1】:

这是另一个纯 Bash 解决方案:

#! /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

它是Shellcheck-clean,并使用 Bash 3 和 Bash 4 进行了测试。

调用者 outarray 获取结果,而不是将它们放入 outarray,因为outarray=( $(perm ...) ) 在任何要洗牌的项目中都不起作用包含空白字符,如果项目包含 glob 元字符,它也可能会中断。没有从 Bash 函数返回重要值的好方法。

如果从另一个函数调用perm,则在调用者中声明outarray(例如使用local -a outarray)将避免创建(或破坏)全局变量。

可以通过无条件地进行交换来安全地简化代码,但代价是与自身进行一些毫无意义的项目交换。

【讨论】:

    【解决方案2】:

    这是我找到的解决方案(它甚至适用于 bash

    Shellchecked 和编辑感谢下面的 cmets。

    #!/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"
    

    它是可以优化的。

    【讨论】:

    • 你应该把所有变量都设为local,否则你会把它们泄露到全局范围内。
    • @chepner 你在项目分配中引用了$@,所以让你像这样传递所有参数:"arg1" "arg2"... 而不是"arg1 arg2..."。不知道什么是最好的。
    • 如果你想对像("foo bar" "1 2 3") 这样的数组进行洗牌,则需要引用。它保留了元素中的空白,而不会将其与分隔元素的空白混淆。
    • @BenjaminW。谢谢你的评论,变量本地化
    • @chepner 非常感谢!
    【解决方案3】:

    你应该使用shuf:

    inarray=("serverA" "serverB" "serverC")
    IFS=$'\n' outarray=($(printf "%s$IFS" "${inarray[@]}" | shuf))
    

    或者当使用带有换行符和其他奇怪字符的数组成员时,使用空分隔字符串:

    inarray=("serverA" "serverB" "serverC")
    readarray -d '' outarray < <(printf "%s\0" "${inarray[@]}" | shuf -z)
    

    【讨论】:

    • 我推荐set -f 使用第一个。请注意,如果我没记错的话,第二个需要bash 4.4 或更高版本。您还可以通过简单的while read 组合为其他版本提供解决方案:)
    【解决方案4】:

    sort 实用程序能够随机打乱列表。

    试试这个:

    servers="serverA serverB serverC serverD"
    for s in $servers ; do echo $s ; done | sort -R
    

    【讨论】:

    • 感谢您的回答,我的排序版本(GNU CoreUtils 5.97)没有 2005 年添加的 -R 选项:O。另外,我用你的回答来记录 sort -R 不是真正的随机播放:基于哈希,它将相同的值组合在一起(如 [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166] 中所述)
    猜你喜欢
    • 2016-12-29
    • 1970-01-01
    • 2016-07-16
    • 1970-01-01
    • 2011-01-27
    • 1970-01-01
    相关资源
    最近更新 更多