bash script: {dir1,dir2,dir3} expansion not working as expected from variable, for picking a random file

I have a problem, with this simple script (pick a random file):

#!/usr/bin/env bash
set -x
srcDir="/home/user/Desktop/wallPapers/{dir1,dir2,dir3}"
randomFile=$(find "$srcDir" -type f -iname "*.jpg" | shuf -n 1)
printf '[%s]\n' $randomFile
set +x

The problem is that while I can type this at the command line (and works perfectly fine):

find /home/user/Desktop/wallPapers/{dir1,dir2,dir3} -type f -iname "*.jpg"

Then the bash debugging set-commands (set -x and +x) tells me, that for some reason bash both encloses the directory string with single quotation marks and it also replaces the double quotation marks with single quotation marks?

./script.sh
+ srcDir='/home/user/Desktop/wallPapers/{dir1,dir2,dir3}'
++ find '/home/user/Desktop/wallPapers/{dir1,dir2,dir3}' -type f -iname '"*.jpg"'
find: ‘/home/user/Desktop/wallPapers/{dir1,dir2,dir3}’: No such file or directory
+ randomFile=
+ printf '[%s]\n'
[]
+ set +x

I understand, this is what bash sees, when the script runs:

find '/home/user/Desktop/wallPapers/{dir1,dir2,dir3}' -type -iname '*.jpg'

And this causes the "No such file or directory"-message, very very annoying... I do not understand, why it inserts these single quotation marks, I want double quotation marks used instead, just like on the command line... Could anyone please explain, I would be happy for that, thanks!


Brace expansion doesn't occur within a variable assignment, as explained here:

Why do tilde prefixes expand prior to assignment, but braces don't

In other contexts, the quotes would have prevented brace expansion as well.

Even if you do manage to get srcDir to expand to a list of directories, quoting it again in the find command will cause it to be treated as a single argument instead of 3 separate paths.

Probably the right way to do this in bash is to use an array:

#!/usr/bin/env bash
set -x
srcDir=("/home/user/Desktop/wallPapers/"{dir1,dir2,dir3})
randomFile=$(find "${srcDir[@]}" -type f -iname "*.jpg" | shuf -n 1)
printf '[%s]\n' "$randomFile"
set +x

As others have already pointed out, the quotes are preventing the brace expansion. You could simplify your script to just:

#!/usr/bin/env bash
printf '[%s]\n' "$(find /home/terdon/Desktop/wallPapers/{dir1,dir2,dir3} -type f -iname "*.jpg" | shuf -n 1)"

Or, if your file names can contain newline characters:

#!/usr/bin/env bash
printf '[%s]\n' "$(find /home/terdon/Desktop/wallPapers/{dir1,dir2,dir3} -type f -iname "*.jpg" -print0| shuf -zn 1)"

If you want something that can run on arbitrary directories and file types, try this:

#!/usr/bin/env bash

targetFilePattern="$1"
shift
declare -a targetDirs=("$@")
echo "find ${targetDirs[@]} -type f -iname '$targetFilePattern' | shuf -n 1"
randomFile=$(find "${targetDirs[@]}" -type f -iname "$targetFilePattern" | shuf -n 1)
echo "$randomFile"

You can then run it as:

printRandomFile '*jpg' /home/terdon/Desktop/wallPapers/{dir1,dir2,dir3}

Or even

printRandomFile '*jpg' /home/terdon/Desktop/wallPapers/{dir1,dir2,dir3,'a dir with a space'}