mivk
mivk

Reputation: 15009

find with $(printf ... array) conditions

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

Answers (2)

Pulkit Kansal
Pulkit Kansal

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

Tom Fenech
Tom Fenech

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

Related Questions