在 Makefile 中使用 rm -i !(*.c|Makefile)?

Using rm -i !(*.c|Makefile) in Makefile?

我有一个目录,其中有一些 C 程序文件和一个包含这些 C 文件的可执行文件的 Makefile。我可以简单地 运行 这一次删除 rm !(*.c|Makefile).

中的所有可执行文件

但是当我将其添加到我的 makefile 时

CFLAGS= -Wall -g -lm

SHELL=/bin/bash
careful:
    echo "nothing"
clean:
    rm -i !(*.c|Makefile)

我在执行命令时遇到此错误

/bin/bash: -c: line 1: syntax error near unexpected token `('
/bin/bash: -c: line 1: `rm -i !(*.c|Makefile)'
make: *** [Makefile:8: clean] Error 2

我不太了解 Bash 语法,但我知道 () 被视为子 shell,我认为这就是我不能 运行 rm (*.c) 的原因直接在终端中,因为 *.c 不是命令(或任何有效语法)。但是 运行ning rm -i !(*.c) 在终端中工作。所以我猜 !() 在 Bash.

中被区别对待

我对这一切的假设: 在 Makefile 中,当我 运行ning make clean 它正在以不同的方式对待 !(*.c|Makefile)比在普通终端,或者不知何故它无视 !(*.c|Makefile)

中的 !

所以我的问题是:

  1. !()() 在 Bash 中是否受到不同对待?
  2. 为什么 !(wildcard) 在终端中工作但在我的 Makefile
  3. 中不工作
  4. 如何让它在 Makefile 中工作?

Bash-specific 扩展通配模式不是开箱即用的非交互式 shells。

!(...) 纯粹是这个通配符语法的一部分,与 subshells 无关。

无论如何,更好的解决方案可能是重构它,这样您就不会依赖 Bash。

.PHONY: clean
clean:
    rm -i $(filter-out $(wildcard *.c) Makefile,$(wildcard *))

Bash 参考手册没有很好地锚定 link,但是 extglob 可用的扩展通配符语法在 Pattern Matching

如果您真的想要,您也可以在 Makefileshopt -s extglob,但这变得相当复杂,因为您必须对解析器隐藏语句,直到 shell 被配置为理解这种语法。例如,

.PHONY: clean
clean:
    shopt -s extglob; \
    eval 'rm -i !(*.c|Makefile)'

还要注意 shopteval 如何需要在同一逻辑行上,因为开箱即用的 make 在单独的新 [=37= 中执行每个配方行] 实例(尽管您可以使用 .ONESHELL 更改它)