/usr/bin/find: 不能动态构建它的参数 [英] /usr/bin/find: cannot build its arguments dynamically
问题描述
以下命令在终端中按预期以交互方式工作.
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
$
我的理解是/是 $ftypes
会在 find
有机会运行之前被 bash
扩展.在这种情况下,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.
这是怎么回事?
非常感谢.
PS:我需要动态构建文件类型列表(上面的 ftypes
变量),以便稍后在脚本中提供给 find
.
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.
推荐答案
到目前为止,两个答案都推荐使用 eval
,但这在引起错误方面享有当之无愧的声誉.这是您可以通过此操作获得的那种奇怪行为的示例:
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
为什么找不到文件 ./a.foo?这是因为 eval
命令是如何被解析的.bash 的解析是这样的(省略了一些不相关的步骤):
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 首先查找引号(尚未找到).
- bash 替换变量(但不会返回并在替换值中查找引号——这首先是导致问题的原因).
- bash 进行通配符匹配(在这种情况下,它会查找匹配
'*.foo'
和'*.bar'
的文件——注意它尚未解析引号,因此它只是将它们视为要匹配的文件名的一部分——并找到'wibble.foo'
并将其替换为'*.foo'
).在此之后,命令大致是eval find .-name "'wibble.foo'" -o "'*.bar'"
.顺便说一句,如果它找到多个匹配项,到最后事情会变得更加愚蠢. - bash 发现该行的命令是
eval
,并在该行的其余部分运行整个解析过程. - bash 再次进行引号匹配,这一次查找两个单引号字符串(因此它将跳过对命令的这些部分的大部分解析).
- bash 查找要替换的变量和要匹配的通配符等,但在命令的未引用部分中没有.
- 最后,bash 运行
find
,将参数."、-name"、wibble.foo"、-o"、-name"和*.bar"传递给它". find
找到*.bar"的一个匹配项,但没有找到wibble.foo"的匹配项.它甚至都不知道您希望它查找*.foo".
- 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".
那么你能做些什么呢?好吧,在这种特殊情况下,添加战略双引号 (eval "find . $ftypes"
) 可以防止虚假的通配符替换,但通常最好完全避免 eval
.当您需要构建命令时,数组是一种更好的方法(请参阅 BashFAQ #050更多讨论):
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')
这篇关于/usr/bin/find: 不能动态构建它的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!