Reputation: 3927
The following command works as expected interactively, in a terminal.
$ find . -name '*.foo' -o -name '*.bar'
./a.foo
./b.bar
$
However, if I do this, I get no results!
$ ftypes="-name '*.foo' -o -name '*.bar'"
$ echo $ftypes
-name '*.foo' -o -name '*.bar'
$ find . $ftypes
$
My understanding was/is that $ftypes
would get expanded by bash
before find
got a chance to run. In which case, the ftypes
approach should also have worked.
What is going on here?
Many thanks in advance.
PS: I have a need to dynamically build a list of file types (the ftypes
variable above) to be given to find
later in a script.
Upvotes: 3
Views: 445
Reputation: 125838
Both answers so far have recommended using eval
, but that has a well-deserved reputation for causing bugs. Here's an example of the sort of bizarre behavior you can get with this:
$ touch a.foo b.bar "'wibble.foo'"
$ ftypes="-name '*.foo' -o -name '*.bar'"
$ eval find . $ftypes
./b.bar
Why didn't it find the file ./a.foo? It's because of exactly how that eval
command got parsed. bash's parsing goes something like this (with some irrelevant steps left out):
'*.foo'
and '*.bar'
-- note that it hasn't parsed the quotes, so it just treats them as part of the filename to match -- and finds 'wibble.foo'
and substitutes it for '*.foo'
). After this the command is roughly eval find . -name "'wibble.foo'" -o "'*.bar'"
. BTW, if it had found multiple matches things would've gotten even sillier by the end.eval
, and runs the whole parsing process over on the rest of the line.find
, passing it the arguments ".", "-name", "wibble.foo", "-o", "-name", and "*.bar".find
finds one match for "*.bar", but no match for "wibble.foo". It never even knows you wanted it to look for "*.foo".So what can you do about this? Well, in this particular case adding strategic double-quotes (eval "find . $ftypes"
) would prevent the spurious wildcard substitution, but in general it's best to avoid eval
entirely. When you need to build commands, an array is a much better way to go (see BashFAQ #050 for more discussion):
$ ftypes=(-name '*.foo' -o -name '*.bar')
$ find . "${ftypes[@]}"
./a.foo
./b.bar
Note that you can also build the options bit by bit:
$ ftypes=(-name '*.foo')
$ ftypes+=(-o -name '*.bar')
$ ftypes+=(-o -name '*.baz')
Upvotes: 7
Reputation: 16368
The problem is that since $ftypes a single quoted value, find does see it as a single argument.
One way around it is:
$ eval find . $ftypes
Upvotes: 1
Reputation: 92345
Simply prefix the line with eval
to force the shell to expand and parse the command:
eval find . $ftypes
Without the eval
, the '*.foo'
is passed on literally instead of just *.foo
(that is, the '
are suddenly considered to be part of the filename, so find
is looking for files that start with a single quote and have an extension of foo'
).
Upvotes: 2