/usr/bin/find:无法动态构建其参数

发布于 2025-01-06 07:37:30 字数 493 浏览 4 评论 0原文

以下命令在终端中按预期交互运行。

$ find . -name '*.foo' -o -name '*.bar'
./a.foo
./b.bar
$

但是,如果我这样做,我不会得到任何结果!

$ ftypes="-name '*.foo' -o -name '*.bar'"
$ echo $ftypes
-name '*.foo' -o -name '*.bar'
$ find . $ftypes
$

我的理解是,在 find 有机会运行之前,$ftypes 会被 bash 扩展。在这种情况下,ftypes 方法也应该有效。

这是怎么回事?

非常感谢。

PS:我需要动态构建文件类型列表(上面的 ftypes 变量),以便稍后在脚本中查找。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心是晴朗的。 2025-01-13 07:37:30

到目前为止,两个答案都建议使用eval,但它因导致错误而享有盛誉。以下是您可能会遇到的奇怪行为的示例:

$ touch a.foo b.bar "'wibble.foo'"
$ ftypes="-name '*.foo' -o -name '*.bar'"
$ eval find . $ftypes
./b.bar

为什么它没有找到文件 ./a.foo?这是因为 eval 命令的解析方式。 bash 的解析是这样的(省略了一些不相关的步骤):

  1. bash 首先查找引号(尚未找到)。
  2. bash 替换变量(但不会返回并在替换值中查找引号——这就是导致问题的首要原因)。
  3. bash 进行通配符匹配(在本例中,它会查找与 '*.foo''*.bar' 匹配的文件 - 请注意,它尚未解析引号,因此它只是将它们视为要匹配的文件名的一部分 - 并找到 'wibble.foo' 并将其替换为 '*.foo')。此后的命令大致是 eval find 。 -name“'wibble.foo'”-o“'*.bar'”。顺便说一句,如果它找到了多个匹配项,那么到最后事情会变得更加愚蠢。
  4. bash 看到该行的命令是 eval,并在该行的其余部分运行整个解析过程。
  5. bash 再次进行引号匹配,这次找到两个单引号字符串(因此它将跳过命令这些部分的大部分解析)。
  6. bash 查找要替换的变量和要匹配的通配符等,但命令的未引用部分中没有任何内容。
  7. 最后,bash 运行 find,并向其传递参数“.”、“-name”、“wibble.foo”、“-o”、“-name”和“*.bar”。
  8. find 找到“*.bar”的一个匹配项,但没有找到“wibble.foo”的匹配项。它甚至不知道您希望它查找“*.foo”。

那么你能对此做些什么呢?好吧,在这种特殊情况下,添加策略性双引号 (eval "find . $ftypes") 可以防止虚假通配符替换,但一般来说,最好完全避免 eval 。当您需要构建命令时,数组是更好的方法(请参阅 BashFAQ #050有关更多讨论):

$ ftypes=(-name '*.foo' -o -name '*.bar')
$ find . "${ftypes[@]}"
./a.foo
./b.bar

请注意,您还可以一点一点地构建选项:

$ ftypes=(-name '*.foo')
$ ftypes+=(-o -name '*.bar')
$ ftypes+=(-o -name '*.baz')

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')
深居我梦 2025-01-13 07:37:30

只需在该行前面添加 eval 即可强制 shell 扩展并解析命令:

eval find . $ftypes

如果没有 eval,则将传递 '*.foo'从字面上看,而不仅仅是 *.foo (也就是说,' 突然被认为是文件名的一部分,因此 find 正在寻找以单引号开头且扩展名为foo')。

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').

扛刀软妹 2025-01-13 07:37:30

问题是,由于 $f 类型是单个带引号的值,因此 find 确实将其视为单个参数。

解决这个问题的一种方法是:

$ eval find . $ftypes

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文