/usr/bin/find: cannot build its arguments dynamically

会有一股神秘感。 提交于 2019-11-26 21:43:12

问题


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):

  1. bash looks for quotes first (none found -- yet).
  2. 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).
  3. 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 roughly eval find . -name "'wibble.foo'" -o "'*.bar'". BTW, if it had found multiple matches things would've gotten even sillier by the end.
  4. bash sees that the command on the line is eval, and runs the whole parsing process over on the rest of the line.
  5. bash does quote matching again, this time finding two single-quoted strings (so it'll skip most parsing on those parts of the command).
  6. bash looks for variables to substitute and wildcards to matching, etc, but there aren't any in the unquoted sections of the command.
  7. Finally, bash runs find, passing it the arguments ".", "-name", "wibble.foo", "-o", "-name", and "*.bar".
  8. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!