如何在makefile中编写'cd'命令?

Dav*_*yan 319 makefile gnu-make

例如,我的makefile中有类似的东西:

all:
     cd some_directory
Run Code Online (Sandbox Code Playgroud)

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

fal*_*tro 564

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

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

例如:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c
Run Code Online (Sandbox Code Playgroud)

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

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c
Run Code Online (Sandbox Code Playgroud)

这在进行破坏性工作时尤其重要,例如清理工作,否则cd如果因任何原因失败,你将摧毁错误的东西.

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

all:
        $(MAKE) -C some_dir all
Run Code Online (Sandbox Code Playgroud)

它将改变some_dir并执行Makefile目标"all".作为最佳实践,请使用$(MAKE)而不是make直接调用,因为它会注意调用正确的make实例(例如,如果您为构建环境使用特殊的make版本),并且在运行时提供稍微不同的行为使用某些开关,例如-t.

对于记录,总是使echos执行它执行的命令(除非明确禁止),即使它没有输出,这就是你所看到的.

  • 两个nits:1.命令应该用`&&`连接,因为```如果目录不存在而且`cd`失败,shell将继续运行当前目录中的其余命令,这可能会导致像编译时出现神秘的"文件未找到"消息,调用make时出现无限循环,或者像`clean :: cd dir这样的规则会导致灾难; rm -rf*`.2.在调用sub-make时,调用`$(MAKE)`而不是`make`,这样[选项将被正确传递](http://www.gnu.org/software/make/manual/make.html #MAKE-变量). (43认同)
  • 好吧,不是*总是*.要抑制回声,只需将@放在行的开头即可. (7认同)
  • @Beta:是的,破折号也忽略了错误状态。也许我有点被带走了,我想指出一个事实,make确实会回显该命令,而不管它是哪种命令。在这种情况下,它是一个没有输出的命令,这会使回显对于不熟悉make的人来说甚至更陌生。 (2认同)
  • @perreal,我通常定义一个模式规则:`%-recursive:`with body:`@T ="$ @"; $(MAKE)-C some_dir $$ {T% - *}`(我通常也有一个for循环,循环遍历一个子目录列表,`$$ {T% - *}`是一个bash扩展,删除了目标名称的`-recursive`部分)然后定义了显式的short-hand每个的(和.PHONY)目标,如`all:all-recursive`,`check:check-recursive`,`clean:clean-recursive`. (2认同)

Chn*_*sos 80

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

  • 新的特殊目标:.ONESHELL指示make调用shell的单个实例并为其提供整个配方,无论它包含多少行.[...]
.ONESHELL:
all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded
Run Code Online (Sandbox Code Playgroud)

  • 请注意`pwd`本身以及`\`pwd \``(带反引号),但`$(shell pwd)`和`$(PWD)`仍然会在执行`cd`之前返回目录命令,所以你不能直接使用它们. (4认同)
  • 是的,因为变量和函数扩展是在make执行命令之前完成的,而`pwd`和`\`pwd \``是由shell本身执行的. (3认同)
  • 不是每个人都在传统的makefile上工作,即便如此,这个答案也是关于*知道*这种可能性存在. (2认同)
  • 在 macOS 上不适合我。 (2认同)

Joe*_*oeS 21

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

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

然后你所要做的就是在你的规则中调用它:

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

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

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

  • "可爱"?更像足够的绳索射击自己的脚. (40认同)
  • 这当然会破坏并行执行(`-jn`),这实际上是_make_的重点. (6认同)
  • 这是一个糟糕的黑客.如果你不得不求助于这样的事情,你就不会使用Makefiles来实现它们的目的. (4认同)
  • 我不会不同意这是一个糟糕的黑客,这是肯定的.但确实展示了你可以做的一些邪恶的事情. (4认同)

Nad*_*LEM 9

它到达那里后你想要它做什么?每个命令都在子shell中执行,因此subshel​​l更改目录,但最终结果是下一个命令仍在当前目录中.

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

BIN=/bin
foo:
    $(shell cd $(BIN); ls)
Run Code Online (Sandbox Code Playgroud)

  • 为什么`$(shell ...)`当`cd $(BIN); ls`或`cd $(BIN)&& ls`(正如@andrewdotn指出的那样)就足够了. (4认同)

Jas*_*sha 6

这是我使用的模式:

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

Run Code Online (Sandbox Code Playgroud)

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

  • 上面的解决方案简单易读(尽管很冗长)
  • 我读了经典论文“Recursive Make Thought Harmful”,这使我不再使用$(MAKE) -C some_dir all
  • 我不想只使用一行代码(用分号或标点&&),因为它的可读性较差,而且我担心在编辑制作配方时会犯错。
  • 我不想使用.ONESHELL特殊目标,因为:
    • 这是一个全局选项,影响 makefile 中的所有配方
    • using.ONESHELL会导致执行配方的所有行,即使较早的行之一因非零退出状态而失败也是如此。诸如调用之类的解决方法set -e是可能的,但是必须为 makefile 中的每个配方实现此类解决方法。