Reputation: 15009
I'm trying to use a bash array of directories to exclude from find
results. However, it doesn't work as I expect, and I would like to understand why.
Example:
mkdir -p findtest
cd findtest
mkdir -p A B C
exclude=(A C)
echo $(printf -- "-not -path '*/%s' " "${exclude[@]}")
This prints -not -path '*/A' -not -path '*/C'
. If use that literally, it works:
find . -not -path '*/A' -not -path '*/C'
.
./B
But using the same command substitution, it doesn't exclude any directory from the output:
find . $(printf -- "-not -path '*/%s' " "${exclude[@]}")
.
./C
./B
./A
Now if I leave out the quotes around the path names to exclude, it actually works:
find . $(printf -- "-not -path */%s " "${exclude[@]}")
.
./B
But that of course doesn't help because there are spaces in some directory names to exclude. And besides, I really don't understand why that would make any difference.
Upvotes: 5
Views: 276
Reputation: 149
You are preparing the command dynamically i.e. the find
command is the output of printf. You need to use eval in such cases so that the shell can process it correctly.
eval find . $(printf -- "-not -path '*/%s' " "${exclude[@]}")
Upvotes: -1
Reputation: 74685
set -x
has the answer to the question "why doesn't it work"?:
$ set -x
$ find . $(printf -- "-not -path '*/%s' " "${exclude[@]}")
++ printf -- '-not -path '\''*/%s'\'' ' A C
+ find . -not -path ''\''*/A'\''' -not -path ''\''*/C'\'''
Well, it is painful to work out what is going on with all of those quotes but it's enough to see that the command isn't what you want.
The important thing is that the single quotes in the string produced by printf
aren't syntactic, i.e. they do not indicate the start and end of an argument. Instead, they are treated as literal quotes, so the find command is excluding directories which start and end with quotes.
One solution would be to use a loop to build your command:
args=()
for e in "${exclude[@]}"; do
args+=( -not -path "*/$e" )
done
find . "${args[@]}"
Upvotes: 5