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

Reply via email to