【问题标题】:How do I write the 'cd' command in a makefile?如何在 makefile 中编写“cd”命令?
【发布时间】:2010-12-19 20:42:17
【问题描述】:

例如,我的 makefile 中有这样的内容:

all:
     cd some_directory

但是当我输入make 时,我只看到'cd some_directory',就像在echo 命令中一样。

【问题讨论】:

  • 不清楚你想做什么,但是,根据我对make 的经验,我从不想像这样更改目录。也许您应该尝试另一种方法来解决您的问题?
  • 认为您的目录很重要是一个常见的新手错误。对于大多数事情,它不是; cd dir; cmd file 几乎总是可以更有用地表示为 cmd dir/file
  • 认为您当前的工作目录无关紧要是一个常见的新手错误。许多程序,尤其是 shell 脚本,在编写时都考虑到了特定值 .。确实,大多数工具的设计方式都不需要更改密码。但这并不总是正确的,我认为将其称为“错误”认为您的目录可能很重要并不是一个好主意。
  • 提示:如果您的 cd 命令显示“没有这样的文件或目录”,即使(相对)目录确实存在,请检查您的 CDPATH 环境变量是否为空或包括“.”。 Make 执行带有“sh”的命令,如果设置了它,它只会通过 CDPATH 找到相对路径。这与 bash 形成对比,后者将尝试 .在咨询 CDPATH 之前。
  • 补充@tripleee 所说的内容(12 年前,是的),有 个实例表明当前目录很重要。例如,在 MacOS 上,zip 命令将在压缩存档的结构中包含整个给定的搜索路径,这可能是不可取的。

标签: makefile gnu-make


【解决方案1】:

它实际上是在执行命令,将目录更改为some_directory,但是,这是在子进程 shell 中执行的,既不会影响 make 也不会影响您正在使用的 shell。

如果您希望在some_directory 中执行更多任务,则需要添加分号并附加其他命令。请注意,您不能使用换行符,因为它们被 make 解释为规则的结尾,因此您为清楚起见使用的任何换行符都需要用反斜杠转义。

例如:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c

还要注意,即使您添加了反斜杠和换行符,每个命令之间也需要分号。这是因为整个字符串被 shell 解析为单行。如 cmets 中所述,您应该使用 '&&' 来连接命令,这意味着它们只有在前面的命令成功时才会被执行。

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c

这在进行破坏性工作(例如清理)时尤其重要,因为如果 cd 因任何原因失败,您将破坏错误的东西。

一个常见的用法是在子目录中调用 make,您可能想要查看它。有一个命令行选项,所以你不必自己打电话给cd,所以你的规则看起来像这样

all:
        $(MAKE) -C some_dir all

这将变为some_dir 并在其中执行Makefile,目标为“all”。作为最佳实践,使用$(MAKE) 而不是直接调用make,因为它会小心调用正确的make 实例(例如,如果您为构建环境使用特殊的make 版本),以及在使用某些开关(例如 -t)运行时提供略有不同的行为。

为了记录,make always 回显它执行的命令(除非明确禁止),即使它没有输出,这就是您所看到的。

【讨论】:

  • 嗯,不是总是。要抑制回声,只需将 @ 放在行首即可。
  • @Beta:是的,破折号前缀也忽略了错误状态。也许我有点得意忘形,我想指出 make 确实会响应命令的事实,无论它是哪种命令。在这种情况下,它是一个没有输出的命令,这使得回显对于不熟悉 make 的人来说显得更加陌生。
  • 两点: 1. 命令应该由&& 真正加入,因为如果目录不存在并且cd 失败,则使用;,shell 将继续运行其余的当前目录中的命令,这可能会导致编译时出现神秘的“找不到文件”消息,调用 make 时出现无限循环,或者像 clean:: cd dir; rm -rf * 这样的规则造成灾难。 2.调用子make时,调用$(MAKE)而不是make,这样options will be passed on correctly
  • @perreal,我通常定义这样的模式规则:%-recursive: 正文:@T="$@";$(MAKE) -C some_dir $${T%-*}(我通常也有一个 for 循环,用于循环子目录列表,@ 987654342@ 是一个 bash 扩展,它删除了目标名称的 -recursive 部分),然后为每个目标定义明确的简写(和 .PHONY)目标,例如 all: all-recursivecheck: check-recursiveclean: clean-recursive
  • @ChristianStewart,是的,正如评论 2 和 3 中所述。
【解决方案2】:

一旦它到达那里,您希望它做什么?每个命令都在一个子shell中执行,所以子shell改变了目录,但最终的结果是下一条命令还在当前目录中。

使用 GNU make,您可以执行以下操作:

BIN=/bin
foo:
    $(shell cd $(BIN); ls)

【讨论】:

  • 为什么$(shell ...)cd $(BIN); lscd $(BIN) && ls(正如@andrewdotn 指出的那样)就足够了。
【解决方案3】:

这是处理目录和制作的一个可爱技巧。而不是使用多行字符串或“cd ;”在每个命令上,定义一个简单的 chdir 函数:

CHDIR_SHELL := $(SHELL)
define chdir
   $(eval _D=$(firstword $(1) $(@D)))
   $(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef

那么你所要做的就是在你的规则中这样称呼它:

all:
          $(call chdir,some_dir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

您甚至可以执行以下操作:

some_dir/myTest:
          $(call chdir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

【讨论】:

  • “可爱”?更像是一根足以射中自己脚的绳子。
  • 这个当前目​​录是为该规则中的命令设置的,还是为所有随后执行的规则设置的?此外,此方法的某些变体是否可以在 Windows 下工作?
  • 这当然会中断并行执行 (-jn),这就是 make 的真正意义所在。
  • 这是一个糟糕的 hack。如果你不得不求助于这样的事情,你并没有使用 Makefile 来实现它们的目的。
  • 我不同意这是一个糟糕的 hack,这是肯定的。但确实展示了你可以做的一些邪恶的事情。
【解决方案4】:

GNU make 3.82(2010 年 7 月)开始,您可以使用 .ONESHELL 特殊目标在 shell 的单个实例中运行所有配方(粗体强调我的):

  • 新的特殊目标:.ONESHELL 指示 make 调用 shell 的单个实例 并为其提供 整个配方,无论它包含多少行。
.ONESHELL: # Applies to every targets in the file!

all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

another_rule:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

注意,这相当于手动运行

$(SHELL) $(.SHELLFLAGS) "cd ~/some_dir; pwd"
# Which gets replaced to this, most of the time:
/bin/sh -c "cd ~/some_dir; pwd"

命令未与&& 链接,因此如果您想在第一个失败的命令处停止,您还应该将-e 标志添加到您的.SHELLFLAGS

.SHELLFLAGS += -e

-o pipefail 标志可能也很有趣:

如果设置,管道的返回值是最后一个(最右边)以非零状态退出的命令的值,或者如果管道中的所有命令成功退出,则返回零。默认情况下禁用此选项。

【讨论】:

  • 请注意pwd 本身有效,`pwd`(带反引号),但$(shell pwd)$(PWD) 仍然会在执行cd 命令之前返回目录,所以你不能直接使用它们。
  • 是的,因为变量和函数扩展是在make执行命令之前完成的,而pwd`pwd`是shell自己执行的。
  • 不是每个人都使用遗留的 makefile,即使这样,这个答案也是关于知道这种可能性存在。
  • 这可能是一个烦人/危险的选项,因为只有目标的最后一个命令会导致失败(任何较早的命令失败都将被忽略),并且可能没有人与您合作。
  • SHELLFLAGS 设置为-e -c,shell 将在第一个失败的命令处退出。
【解决方案5】:

像这样:

target:
    $(shell cd ....); \
    # ... commands execution in this directory
    # ... no need to go back (using "cd -" or so)
    # ... next target will be automatically in prev dir

祝你好运!

【讨论】:

  • 不,这是错误的。具体来说,$(shell cd ....) 在最初解析 Makefile 时执行,而不是在运行此特定配方时执行。
  • @triplee 不完全——$(shell) 仅在 make 决定构建 target 时扩展。如果 make 永远不需要配方,它就不会扩展它。
【解决方案6】:

改变目录

foo: 
    $(MAKE) -C mydir

multi:
    $(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir

【讨论】:

    【解决方案7】:

    这是我使用的模式:

    .PHONY: test_py_utils
    PY_UTILS_DIR = py_utils
    test_py_utils:
        cd $(PY_UTILS_DIR) && black .
        cd $(PY_UTILS_DIR) && isort .
        cd $(PY_UTILS_DIR) && mypy .
        cd $(PY_UTILS_DIR) && pytest -sl .
        cd $(PY_UTILS_DIR) && flake8 .
    
    

    我采用这种模式的动机是:

    • 上述解决方案简单易读(虽然冗长)
    • 我阅读了经典论文"Recursive Make Considered Harmful",这使我不鼓励使用$(MAKE) -C some_dir all
    • 我不想只使用一行代码(用分号或&& 标点符号),因为它的可读性较差,而且我担心在编辑 make 配方时会打错字。
    • 我不想使用.ONESHELL 特殊目标,因为:
      • 这是一个影响 makefile 中所有配方的全局选项
      • 使用.ONESHELL 会导致配方的所有行都被执行,即使前面的行之一因非零退出状态而失败。像调用 set -e 这样的变通方法是可能的,但必须为 makefile 中的每个配方实施这种变通方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-18
      相关资源
      最近更新 更多