问题
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.
回答1:
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):
- bash looks for quotes first (none found -- yet).
- bash substitutes variables (but doesn't go back and look for quotes in the substituted values -- this is what lead to the problem in the first place).
- bash does wildcard matching (in this case it looks for files matching
'*.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 roughlyeval find . -name "'wibble.foo'" -o "'*.bar'"
. BTW, if it had found multiple matches things would've gotten even sillier by the end. - bash sees that the command on the line is
eval
, and runs the whole parsing process over on the rest of the line. - bash does quote matching again, this time finding two single-quoted strings (so it'll skip most parsing on those parts of the command).
- bash looks for variables to substitute and wildcards to matching, etc, but there aren't any in the unquoted sections of the command.
- Finally, bash runs
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')
回答2:
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'
).
回答3:
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
来源:https://stackoverflow.com/questions/9308606/usr-bin-find-cannot-build-its-arguments-dynamically