如何在Makefile中使用for、find和通配符?

如何在Makefile中使用for、find和通配符?,makefile,Makefile,到目前为止,我正在尝试做以下工作,但没有取得任何成功: remove: for file in $(shell find src/ -name migrations -type d); do rm $(wildcard "$(file)/0*.py"); done 如果我理解清楚,您希望删除所有名为: src/**/migrations/0*.py 其中,**可以是任何内容,包括任何内容查找可以单独执行此操作: find src -path '*/migrations/0*.py'

到目前为止,我正在尝试做以下工作,但没有取得任何成功:

remove:
    for file in $(shell find src/ -name migrations -type d); do rm $(wildcard "$(file)/0*.py"); done

如果我理解清楚,您希望删除所有名为:

src/**/migrations/0*.py
其中,
**
可以是任何内容,包括任何内容<代码>查找可以单独执行此操作:

find src -path '*/migrations/0*.py' -delete
由于make配方只是简单的外壳,您不需要
$(外壳…
或类似的东西:

remove:
    find src -path '*/migrations/0*.py' -delete
但在运行此潜在危险规则之前,您可能应该使用此规则,直到您满意为止:

remove:
    find src -path '*/migrations/0*.py' -ok rm {} \;
唯一的区别是,它会在删除文件之前要求您确认。我们还可以让所有这些变得更简单:

remove-safe: DELCMD := -ok rm {} \\;
remove-unsafe: DELCMD := -delete
remove-dry-run: DELCMD := -print

remove-safe remove-unsafe remove-dry-run:
    find src -path '*/migrations/0*.py' $(DELCMD)
根据您的需要使用一个或另一个目标:

  • remove dry run
    打印将被删除的文件列表,而不删除它们
  • remove safe
    要删除文件并确认
  • remove unsafe
    可在未经确认的情况下删除文件
编辑1:如果这是一个关于循环和生成的练习,请记住生成配方是shell脚本(每行一个),首先由make展开,然后传递到shell。因此:

  • 如果要使用shell变量,请在一行中使用它们(但可以使用尾随的
    \
    )分隔行。否则,它们将来自不同的shell调用,无法按预期工作
  • 如果您使用
    $
    登录您的shell扩展配方,请将它们加倍以通过make退出第一次扩展
  • 在下面的例子中,我假设您没有带空格或特殊字符的目录名或文件名。如果您有,是时候问一个关于shell的问题了

    因此,例如,使用bash shell,我们可以首先构建一个目标目录数组,然后在其上循环以删除文件:

    SHELL := bash
    
    remove:
        dirs=( $$(find src -type d -name migrations) ); \
        for d in "$${dirs[@]}"; do \
            rm -f "$$d"/0*.py; \
        done
    
    编辑2:您还可以使用内置的生成循环系统,每个目录有一个(假的)干净目标

    DIRS := $(shell find src -type d -name migrations)
    
    clean-targets := $(addprefix clean-,$(DIRS))
    
    .PHONY: remove $(clean-targets)
    
    remove: $(clean-targets)
    
    $(clean-targets): clean-%:
        rm -f "$*"/0*.py
    
    棘手的部分是:

    $(clean-targets): clean-%:
        rm -f "$*"/0*.py
    
    规则。它相当于每个
    migrations
    目录有一条规则,即:

    clean-DIR: clean-%:
        rm -f "$*"/0*.py
    
    其中,
    DIR
    是目录路径。这些规则是。在配方中,
    $*
    make自动变量由make展开,作为
    clean-%
    模式的主干。因此,如果
    DIR
    src/foo/bar/migrations
    ,则其规则等效于:

    clean-src/foo/bar/migrations:
        rm -f src/foo/bar/migrations/0*.py
    
    与shell循环相比,此样式的主要优点是make可以并行运行所有配方。如果您有数百个
    迁移
    目录,并且您的计算机上有8个或16个内核,那么它确实可以带来不同。请尝试:

    make -j1 remove
    make -j16 remove
    

    你会看到区别。

    简单的回答是你不能那样做

    编写makefile配方时,您必须清楚了解它们的工作原理:首先,make将扩展所有make变量和函数。然后,make将结果字符串传递给要运行的shell。最后,make将等待shell完成并检查退出代码,以确定是否成功

    这里您试图使用shell
    for
    循环,其中循环体调用make
    通配符
    函数。这无法工作

    考虑你想要做什么(和外壳)要做的事情:shell将运行其for循环,然后每次执行循环体时,它都会返回通信以生成某个shell变量的值,make将使用该变量展开make通配符函数,然后将结果返回给shell进行更多处理。这是不可能的

    最好是使用shell构造编写配方。一个好的经验法则是,在配方中使用make的
    shell
    函数从来都不是一个好主意:配方已经在shell中运行,因此您根本不需要
    shell
    函数。这很容易混淆。同样,您很少需要make的
    通配符函数,因为shell会自动为您创建文件

    我将这样写:

    remove:
            find src/ -name migrations -type d | while read -r dir; do rm "$$file"/0*.py; done
    

    注意,我们用两个美元符号转义了
    $$file
    ,以防止make将其扩展为make变量。

    我知道我可能可以用
    find…-exec rm-fr{}来实现这一点\;
    但是问题更多的是关于在makefileOr中使用通配符和带有shell调用的循环,或者按照Renaud的建议,使用没有shell循环的just find。