On Thu, Oct 22, 2026 at 15:37:09 +0200, Franco Martelli wrote:
> On 21/10/26 at 21:04, Greg Wooledge wrote:
> > 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're describing a purpose that the example was not designed to do.
That example just chooses *one* file. It doesn't loop or anything.
Anyone reading the example and the text above it can immediately see
that if they do this multiple times in a row, they may get the same
file more than once. That's the nature of random numbers. It's
obvious and doesn't need to be stated.
> > 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
$i doesn't need to be used. It's only there to make the loop run 10
times, which the {1..10} already does. That's bash brace expansion,
and it's equivalent to writing out 1 2 3 4 5 6 7 8 9 10. The loop could
have been written:
for i in 1 2 3 4 5 6 7 8 9 10; do
...
done
But {1..10} is shorter.
Incrementing i inside the loop would serve no purpose. It's already
iterating over a list of 10 strings. For that matter, the fact that
the strings are numeric doesn't even matter. It could just as well
have been written:
for i in alfa bravo charlie delta echo foxtrot golf hotel india juliett; do
...
done
> #!/bin/bash
>
> cd ~/Video/clips/partyzone/
If this fails, you want to abort, so put || exit at the end of this.
> 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
This expansion should be quoted. Or use [[ ]] instead. Either one's
fine.
> 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
Iterating over the array by index works, but it would be simpler to
iterate over the values instead.
for index in "${playlist[@]}"; do
clip=${files[index]}
ffplay -vf ... "$clip"
done
One final note: it looks like you *are* trying to generate a playlist
that contains every single file in your directory. If the number of
files is small (let's say, 100 or fewer), your approach might be
acceptable, but with larger directories, you're going to spend a long
time waiting for those last few files to be selected.
For this project, it would be better to shuffle the array of files
instead of selecting one at a time.
#!/bin/bash
# Gather the files.
cd ~/Video/clips/partyzone/ || exit
playlist=([0-9][0-9][0-9][0-9]*)
# Shuffle the playlist.
n=${#playlist[@]}
for ((i=n-1; i>0; i--)); do
max=$(( 32768 / (i+1) * (i+1) ))
while (( (rand=RANDOM) >= max )); do :; done
rand=$(( rand % (i+1) ))
tmp=${playlist[i]} playlist[i]=${playlist[rand]} playlist[rand]=$tmp
done
# Play the shuffled playlist.
for clip in "${playlist[@]}"; do
ffplay -vf "scale=720:576,setdar=720/576" -autoexit -volume 10 \
"$clip" >/dev/null 2>&1
done
You can add your trap if you want it.