On 21/10/26 at 21:04, Greg Wooledge wrote:
On Wed, Oct 21, 2026 at 20:30:25 +0200, Franco Martelli wrote:
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?
https://mywiki.wooledge.org/BashFAQ/026
Very nice, at the snippet for playing a song or seeing an image:
...
# Bash
files=(*.txt)
n=${#files[@]}
randfile=${files[RANDOM % n]}
...
I would mention that it's possible that a file will be pick twice. It
needs an array populated with unique random values and then pick the
"randfile" from "files[]" using that array as index value, see my code
below.
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?
Well, usually you want 0 to n-1 because arrays are indexed from 0. If
you want 1 to n, you can use something that gives you 0 to n-1, and
then add 1 to it.
Given an array of n elements, if we first store the size in a variable
for convenience:
n=${#array[@]}
then the *easiest* way to select a random element (0 to n-1) is to use:
${array[RANDOM % n]}
But as FAQ 26 explains, this is imperfect. There is a bias toward the
lower numbers (unless n is an exact divisor of 32768). So, you have
to decide whether you can live with that bias or not. For something
that's just generating a playlist of songs, this may be good enough.
Otherwise, you can use a function like this one from FAQ 26:
rand() {
local max=$((32768 / $1 * $1))
while (( (r=RANDOM) >= max )); do :; done
r=$(( r % $1 ))
}
You'd use it like this:
clips=(...)
n=${#clips[@]}
declare -A chosen
playlist=()
for i in {1..10}; do
# Get an unused clip, which may take several tries.
while true; do
rand "$n" # Get a clip index number in global variable r.
[[ ${chosen[$r]} ]] && continue
# If we get to this point, it's a new one.
chosen[r]=1
playlist+=( "${clips[r]}" )
break
done
done
$i isn't used inside the loop, I would expect an ((i++)) at the bottom
of the "for i in {1..10}; do" loop
That generates a "playlist" array with 10 randomly selected, unique
elements from the "clips" array. I didn't add any sanity checking,
so there are some obvious issues, like an infinite loop if clips
has fewer than 10 elements. But it should be a decent starting point.
Another algorithm for solving this same problem would be to randomly
shuffle the clips array, and then use the first 10 elements as your
playlist. I won't include code for that, but you should be able to
do that using the examples in FAQ 26.
When I write code I try to avoid complexity if not strictly necessary so
I think I'll go with the following script:
...
#!/bin/bash
cd ~/Video/clips/partyzone/
declare -a rndarr
declare -a playlist
files=([0-9][0-9][0-9][0-9]*)
total=${#files[@]}
index=0
# Populate playlist array with unique random values
while [ "$index" -lt "$total" ]; do
clip=$((RANDOM % total))
if [ -z ${rndarr[clip]} ]; then
rndarr[$clip]=1
playlist[$index]=$clip
((index++))
fi
done
# Trap Ctrl+C to exit immediately so the script doesn't continue past ffplay
trap 'echo -e "\nStopping script..."; exit 130' INT
index=0
while [ "$index" -lt "$total" ]; do
clip=${files[playlist[index]]}
# /usr/bin/ffplay -vf "scale=720:576,setdar=720/576" -autoexit -volume
10 "$clip" >/dev/null 2>&1
echo $clip
((index++))
done
...
It's readable enough and simple, thank again,
--
Franco Martelli