On 21/10/26 at 17:07, Greg Wooledge wrote:
On Wed, Oct 21, 2026 at 16:45:30 +0200, Franco Martelli wrote:
# Populate playlist array with unique random values
while [ "$index" -le "$total" ]; do
clip="$(/usr/bin/numrandom /1..$total/)"
I would try to avoid calling an external program inside this loop,
especially if the purpose of the external program is just to choose
a random number between 1 and n. That can be done with a non-forking
bash function call.
First of all thanks Greg for your patience explaining me coding. I'm
interested about avoid to call an external tool inside the loop, do you
have a snippet of code that provides random numbers in a specified range
using a bash function?
if [[ ! ${playlist[@]} =~ "$clip" ]]; then # [ -z
${rndarr[clip]} ]; then
# rndarr[$clip]=1
playlist[$index]=$clip
((index++))
fi
done
It works fine if I use the second array "rndarr[]". I use that array to
store 1 at the rndarr[$index] occurrence then, after incremented the index,
I check whether it's empty (no 1 stored) at the new $index value.
My wishes is to avoid to use a second array (rndarr[]) and use playlist[]
array only to do this, but with the above code I have an endless loop, why?
OK, first note: using an associative array would be the typical way to
store a set of "things that have already been chosen". Your "rndarr"
seems to be an indexed array, which I guess will also work as long
as the "thing chosen" is a non-negative integer.
Trying to do this without a second array means you need to search through
the entire first array every time to see whether the thing is already
in the array. That's an O(n) operation, while looking for an element
in an associative array (or your second indexed array) should be a much
faster O(1) operation.
Second note: [[ ! ${x[@]} =~ "$i" ]] is not even a safe way to check
for the existence of an element "$i" in an array "x". Consider the
case where i = 5, and you've got 15 and 50 already in the array. The
expansion of ${x[@]} will include the text "15 50", and doing an =~ 5
against that text will yield a false positive.
Yes I was afraid of this, but I wasn't sure that it happened.
Third note: many people, at this point, will cobble together some code
like this:
IFS=:
[[ ":${x[*]}:" =~ ":$i:" ]] ...
unset IFS
which compares the string :15:50: against the regex :5:. This works,
sort of. But you're expanding the *entire* array as a string on
every single loop iteration, generating a regular expression and
applying it... it's overkill. You can reduce the regular expression
to a substring match:
IFS=:
[[ ":${x[*]}:" = *":$i:"* ]] ...
unset IFS
which is slightly better, but you're still copying the entire array
every single iteration.
Fourth note: if you want to check whether $i is in the array x[],
the proper way is to *use a loop*:
found=0
for element in "${x[@]}"; do
if [[ $element = "$i" ]]; then
found=1
break
fi
done
This avoids copying the entire array, and it allows you to stop early
once you find the element.
Honestly, I would just use the associative array. It'll be much more
efficient than anything else you can do.
OK, I'll stay with my first version that uses a second array to mark a
random value as assigned:
...
# Populate playlist array with unique random values
while [ "$index" -le "$total" ]; do
clip="$(/usr/bin/numrandom /1..$total/)"
if [ -z ${rndarr[clip]} ]; then
rndarr[$clip]=1
playlist[$index]=$clip
((index++))
fi
done
...
~$ time ./rndclips
1 120 93 30 13 104 113 28 18 101 12 131 15 107 109 58 36 75 76 57 64 45
47 67 19 63 53 116 94 81 59 84 72 4 31 6 51 82 3 122 87 10 89 42 35 54
90 96 99 38 34 95 16 136 25 5 21 123 80 133 79 66 60 132 39 8 11 118 49
22 134 44 29 86 17 14 20 83 9 43 2 105 65 61 23 41 70 37 7 121 110 48
125 97 73 74 27 108 68 78 103 69 50 126 106 119 62 1 88 135 52 114 92 85
55 100 111 46 26 124 77 98 102 33 117 112 56 129 71 130 24 91 32 40 128
115 127
real 0m5,228s
user 0m3,922s
sys 0m1,350s
This works, that was the time it takes to populate an array of 137
elements. But it still suffers to fork a sub-process much more times of
137 iterations.
Fifth note: you want to know why your loop never terminates, but I
can't give you an answer to that. I'd suggest turning on set -x to
see what the script is doing.
I prefer to not investigate because traverse an entire array for each
iteration it is not the right things to do (I changed my mind).
I checked the remarkable ABS ยน handbook
The most remarkable thing about the ABS is how many flaws it contains.
:) but I learnt my Bash scripting reading ABS and I suppose I'm not
alone, for instance at page #431 :
...
# Substring Replacement
# Replace first occurrence of substring with replacement.
echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
# ^ # Applied to all elements of the array.
...
This made me thinking that there was a standard way to query the array
for a value.
You talked me about a Bash function able to return a random value
between 1 and n, at page #107 of the ABS I see that I have to use the
'modulo' operator to apply a range to a variable assigned with $RANDOM
but it doesn't explain how to start from 1 and not from 0. Do I have to
accept ranges from 0 to n and then discard the 0?
Kind regards,
--
Franco Martelli