how to randomly loop over an array (shuffle) in bash [duplicate]










2
















This question already has an answer here:



  • Simple method to shuffle the elements of an array in BASH shell?

    3 answers



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.










share|improve this question















marked as duplicate by tripleee bash
Users with the  bash badge can single-handedly close bash questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 14 '18 at 5:09


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.















  • Neither inarray nor outarray are actually arrays.

    – chepner
    Nov 9 '18 at 16:59











  • And both assignments are syntactically invalid.

    – Benjamin W.
    Nov 9 '18 at 17:02











  • sorry, pseudocode, I edit my question

    – JoKoT3
    Nov 9 '18 at 17:05











  • I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

    – chepner
    Nov 9 '18 at 17:18











  • trust me, real use case here :)

    – JoKoT3
    Nov 9 '18 at 17:43















2
















This question already has an answer here:



  • Simple method to shuffle the elements of an array in BASH shell?

    3 answers



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.










share|improve this question















marked as duplicate by tripleee bash
Users with the  bash badge can single-handedly close bash questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 14 '18 at 5:09


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.















  • Neither inarray nor outarray are actually arrays.

    – chepner
    Nov 9 '18 at 16:59











  • And both assignments are syntactically invalid.

    – Benjamin W.
    Nov 9 '18 at 17:02











  • sorry, pseudocode, I edit my question

    – JoKoT3
    Nov 9 '18 at 17:05











  • I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

    – chepner
    Nov 9 '18 at 17:18











  • trust me, real use case here :)

    – JoKoT3
    Nov 9 '18 at 17:43













2












2








2









This question already has an answer here:



  • Simple method to shuffle the elements of an array in BASH shell?

    3 answers



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.










share|improve this question

















This question already has an answer here:



  • Simple method to shuffle the elements of an array in BASH shell?

    3 answers



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.





This question already has an answer here:



  • Simple method to shuffle the elements of an array in BASH shell?

    3 answers







arrays bash random shuffle






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 9 '18 at 17:06







JoKoT3

















asked Nov 9 '18 at 16:11









JoKoT3JoKoT3

464




464




marked as duplicate by tripleee bash
Users with the  bash badge can single-handedly close bash questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 14 '18 at 5:09


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.






marked as duplicate by tripleee bash
Users with the  bash badge can single-handedly close bash questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 14 '18 at 5:09


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.














  • Neither inarray nor outarray are actually arrays.

    – chepner
    Nov 9 '18 at 16:59











  • And both assignments are syntactically invalid.

    – Benjamin W.
    Nov 9 '18 at 17:02











  • sorry, pseudocode, I edit my question

    – JoKoT3
    Nov 9 '18 at 17:05











  • I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

    – chepner
    Nov 9 '18 at 17:18











  • trust me, real use case here :)

    – JoKoT3
    Nov 9 '18 at 17:43

















  • Neither inarray nor outarray are actually arrays.

    – chepner
    Nov 9 '18 at 16:59











  • And both assignments are syntactically invalid.

    – Benjamin W.
    Nov 9 '18 at 17:02











  • sorry, pseudocode, I edit my question

    – JoKoT3
    Nov 9 '18 at 17:05











  • I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

    – chepner
    Nov 9 '18 at 17:18











  • trust me, real use case here :)

    – JoKoT3
    Nov 9 '18 at 17:43
















Neither inarray nor outarray are actually arrays.

– chepner
Nov 9 '18 at 16:59





Neither inarray nor outarray are actually arrays.

– chepner
Nov 9 '18 at 16:59













And both assignments are syntactically invalid.

– Benjamin W.
Nov 9 '18 at 17:02





And both assignments are syntactically invalid.

– Benjamin W.
Nov 9 '18 at 17:02













sorry, pseudocode, I edit my question

– JoKoT3
Nov 9 '18 at 17:05





sorry, pseudocode, I edit my question

– JoKoT3
Nov 9 '18 at 17:05













I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

– chepner
Nov 9 '18 at 17:18





I question the need to shuffle an array in a script that wouldn't be more appropriately written in another language.

– chepner
Nov 9 '18 at 17:18













trust me, real use case here :)

– JoKoT3
Nov 9 '18 at 17:43





trust me, real use case here :)

– JoKoT3
Nov 9 '18 at 17:43












4 Answers
4






active

oldest

votes


















2














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.






share|improve this answer
































    1














    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.






    share|improve this answer




















    • 1





      You should make all variables local, or you leak them to global scope.

      – Benjamin W.
      Nov 9 '18 at 17:03











    • @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

      – JoKoT3
      Nov 9 '18 at 17:38






    • 1





      Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

      – chepner
      Nov 9 '18 at 17:41











    • @BenjaminW. thanks for you remark, variables localized

      – JoKoT3
      Nov 9 '18 at 17:41











    • @chepner great thanks !

      – JoKoT3
      Nov 9 '18 at 17:42


















    0














    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





    share|improve this answer























    • Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

      – JoKoT3
      Nov 9 '18 at 17:01



















    -1














    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" "$inarray[@]" | shuf -z)





    share|improve this answer

























    • I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

      – PesaThe
      Nov 9 '18 at 17:59


















    4 Answers
    4






    active

    oldest

    votes








    4 Answers
    4






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    2














    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.






    share|improve this answer





























      2














      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.






      share|improve this answer



























        2












        2








        2







        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.






        share|improve this answer















        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.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 12 '18 at 18:31

























        answered Nov 9 '18 at 20:50









        pjhpjh

        1,669611




        1,669611























            1














            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.






            share|improve this answer




















            • 1





              You should make all variables local, or you leak them to global scope.

              – Benjamin W.
              Nov 9 '18 at 17:03











            • @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

              – JoKoT3
              Nov 9 '18 at 17:38






            • 1





              Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

              – chepner
              Nov 9 '18 at 17:41











            • @BenjaminW. thanks for you remark, variables localized

              – JoKoT3
              Nov 9 '18 at 17:41











            • @chepner great thanks !

              – JoKoT3
              Nov 9 '18 at 17:42















            1














            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.






            share|improve this answer




















            • 1





              You should make all variables local, or you leak them to global scope.

              – Benjamin W.
              Nov 9 '18 at 17:03











            • @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

              – JoKoT3
              Nov 9 '18 at 17:38






            • 1





              Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

              – chepner
              Nov 9 '18 at 17:41











            • @BenjaminW. thanks for you remark, variables localized

              – JoKoT3
              Nov 9 '18 at 17:41











            • @chepner great thanks !

              – JoKoT3
              Nov 9 '18 at 17:42













            1












            1








            1







            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.






            share|improve this answer















            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.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 13 '18 at 14:11

























            answered Nov 9 '18 at 16:25









            JoKoT3JoKoT3

            464




            464







            • 1





              You should make all variables local, or you leak them to global scope.

              – Benjamin W.
              Nov 9 '18 at 17:03











            • @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

              – JoKoT3
              Nov 9 '18 at 17:38






            • 1





              Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

              – chepner
              Nov 9 '18 at 17:41











            • @BenjaminW. thanks for you remark, variables localized

              – JoKoT3
              Nov 9 '18 at 17:41











            • @chepner great thanks !

              – JoKoT3
              Nov 9 '18 at 17:42












            • 1





              You should make all variables local, or you leak them to global scope.

              – Benjamin W.
              Nov 9 '18 at 17:03











            • @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

              – JoKoT3
              Nov 9 '18 at 17:38






            • 1





              Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

              – chepner
              Nov 9 '18 at 17:41











            • @BenjaminW. thanks for you remark, variables localized

              – JoKoT3
              Nov 9 '18 at 17:41











            • @chepner great thanks !

              – JoKoT3
              Nov 9 '18 at 17:42







            1




            1





            You should make all variables local, or you leak them to global scope.

            – Benjamin W.
            Nov 9 '18 at 17:03





            You should make all variables local, or you leak them to global scope.

            – Benjamin W.
            Nov 9 '18 at 17:03













            @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

            – JoKoT3
            Nov 9 '18 at 17:38





            @chepner you quoted $@ in items assignement, making so make you pass all arguments like this : "arg1" "arg2"... instead on "arg1 arg2...". not sure what is best.

            – JoKoT3
            Nov 9 '18 at 17:38




            1




            1





            Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

            – chepner
            Nov 9 '18 at 17:41





            Quoting is necessary if you want to shuffle an array like ("foo bar" "1 2 3"). It preserves the whitespace in the elements without confusing it with the whitespace that separates the elements.

            – chepner
            Nov 9 '18 at 17:41













            @BenjaminW. thanks for you remark, variables localized

            – JoKoT3
            Nov 9 '18 at 17:41





            @BenjaminW. thanks for you remark, variables localized

            – JoKoT3
            Nov 9 '18 at 17:41













            @chepner great thanks !

            – JoKoT3
            Nov 9 '18 at 17:42





            @chepner great thanks !

            – JoKoT3
            Nov 9 '18 at 17:42











            0














            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





            share|improve this answer























            • Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

              – JoKoT3
              Nov 9 '18 at 17:01
















            0














            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





            share|improve this answer























            • Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

              – JoKoT3
              Nov 9 '18 at 17:01














            0












            0








            0







            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





            share|improve this answer













            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






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 9 '18 at 16:35









            squeamish ossifragesqueamish ossifrage

            16.8k32361




            16.8k32361












            • Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

              – JoKoT3
              Nov 9 '18 at 17:01


















            • Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

              – JoKoT3
              Nov 9 '18 at 17:01

















            Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

            – JoKoT3
            Nov 9 '18 at 17:01






            Thanks for your answer, my version of sort (GNU CoreUtils 5.97) does not have the -R option which was added in 2005 :O. Also, I use you answer to document that sort -R is not a true shuffle : being based on hash, it group together identical value (as documented in [bugs.debian.org/cgi-bin/bugreport.cgi?bug=641166]

            – JoKoT3
            Nov 9 '18 at 17:01












            -1














            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" "$inarray[@]" | shuf -z)





            share|improve this answer

























            • I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

              – PesaThe
              Nov 9 '18 at 17:59
















            -1














            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" "$inarray[@]" | shuf -z)





            share|improve this answer

























            • I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

              – PesaThe
              Nov 9 '18 at 17:59














            -1












            -1








            -1







            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" "$inarray[@]" | shuf -z)





            share|improve this answer















            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" "$inarray[@]" | shuf -z)






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 9 '18 at 17:57

























            answered Nov 9 '18 at 17:50









            Kamil CukKamil Cuk

            9,8071527




            9,8071527












            • I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

              – PesaThe
              Nov 9 '18 at 17:59


















            • I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

              – PesaThe
              Nov 9 '18 at 17:59

















            I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

            – PesaThe
            Nov 9 '18 at 17:59






            I recommend set -f with the first one. And note you will need bash 4.4 or later for the second one if I am not mistaken. You might also throw in a solution with a simple while read combo for other versions :)

            – PesaThe
            Nov 9 '18 at 17:59




            這個網誌中的熱門文章

            Barbados

            How to read a connectionString WITH PROVIDER in .NET Core?

            Node.js Script on GitHub Pages or Amazon S3