问题 在makefile中处理管道及其退出状态的最佳方法


如果命令失败 make, 如 gcc,它退出...

gcc
gcc: fatal error: no input files
compilation terminated.
make: *** [main.o] Error 4

但是,如果我有一个管道,则会获取管道中最后一个命令的退出状态。举个例子, gcc | cat,不会失败,因为 cat 成功。

我知道整个管道的退出代码存储在 PIPESTATUS 数组,我可以得到错误代码4 ${PIPESTATUS[0]}。我应该如何构建我的makefile来处理管道命令并正常退出?


在评论中,另一个例子是 gcc | grep something。在这里,我假设最理想的行为仍然是 gcc 而且只有 gcc 导致失败而不是 grep 如果没找到任何东西。


9082
2017-08-14 11:18


起源

退后一步,你能完全避开管道吗? - chepner
@chepner是可能的吗?我想不出一种不使用临时文件或命名管道的方法。 cat <( gcc ) 仍然有同样的问题。 - jozxyqk
我虽然 gcc | cat 只是一个例子;我想不出有任何理由真正这样做。如果您需要将输出保存在其他位置, gcc > .... 应该管用。如果 cat 是一个更复杂命令的占位符,可能还有其他选项。 - chepner
@chepner是的, gcc | grep error 或某事可能是一个更好的例子。 - jozxyqk
如果您希望构建过程中止if gcc 失败,我只是将其输出/错误重定向到一个文件,然后只处理该文件 gcc 成功。只因为你 能够 使用烟斗并不代表你 应该。 - chepner


答案:


你应该能告诉make使用 bash 代替 sh 得到 bash 具有 set -o pipefail 设置,以便它在管道中的第一次失败时退出。

GNU Make 3.81(大概早些时候,虽然我不确定)你应该能够做到这一点 SHELL = /bin/bash -o pipefail

GNU Make 3.82(和更新)你应该能够做到这一点 SHELL = /bin/bash 和 .SHELLFLAGS = -o pipefail -c (虽然我不知道是否加入 -c 到目前为止这是必要的,或者即使你指定make也会为你添加 .SHELLFLAGS

来自 bash 手册页:

管道的返回状态是最后一个的退出状态   命令,除非启用了pipefail选项。如果是pipefail   启用后,管道的返回状态是最后一个的值   (最右边)命令以非零状态退出,如果全部退出则为零   命令退出成功。如果保留字!先于a   管道,该管道的退出状态是逻辑否定   退出状态如上所述。 shell等待所有命令   在管道中在返回值之前终止。


9
2017-08-14 11:58



这很好用。我确实需要 -c 旗。我想这里要注意的一件事就是 gcc | grep 如果没有找到任何内容,grep可能会导致失败。 - jozxyqk
如果只需要一条生产线, set -o pipefail ; gcc | cat 会是等同的。 - jozxyqk
是的,任何全局修改默认状态的东西都会产生副作用。 - Etan Reisner
我个人认为任何使用的东西 gcc | grep 在一个makefile规则上下文已经走错了方向。 - Etan Reisner


我会去的 pipefail。但如果你真的不想要(要么 如果你只想在第一个进程上失败 - 不是在管道的其余部分发生故障的情况下:

SHELL=bash

all:
        gcc | cat ; exit "$${PIPESTATUS[0]}"

与@jozxyqk自我回答相比,唯一的优点是您不会丢失退出状态代码。


5
2017-08-14 15:12





只需添加您的开头即可 makefile 命令:

SHELL=/bin/bash -o pipefail

现在,您可以生成 errors.err 来自对象的文件(第一规则),不用担心它会被可执行文件覆盖(第二规则)。

%.o : %.c
    gcc $(CFLAGS) $(CPPFLAGS) $^ -o $@ 2>&1 | tee errors.err

%.x : %.o $(OBJECTS)
    gcc $(LDLIBS) $^ -o $@ 2>&1 | tee errors.err

没有它, make 从规则1中获取没有错误,并运行规则2,覆盖它。你最终只会有一行 errors.err 声明没有要运行的目标文件 gcc

gcc: error: program.o: No such file or directory

1
2017-07-11 22:04



当构建是并行的时(例如,Make被称为 make -j),errors.err将被破坏。最高投票的答案已经给出了使用Bash管道故障的建议。 - Palec
西尔万的答案表明 pipefail 但用途 SHELL=bash。设置此而不是使用它需要一些知识 setopt。无论如何,我将编辑我的答案,通过并行调用来指出问题 make。谢谢。 - Dr Beco
program.x errors.err: program.c 不 不 告诉make该命令同时生成两个输出。它告诉make该命令可用于生成 或 这些文件(但它们每个都由它自己制作,因此不止一次运行它是正确的)。 - Etan Reisner
您好@EtanReisner,tx评论。现在,我没有说它同时产生两者,只是在我的测试中它阻止了破坏。无论如何,我很欣赏对downvote的评论。没有它,很难理解可能出错的地方。如果您还有其他任何内容可以分享,欢迎提出。干杯。 - Dr Beco
我的观点是,它并没有阻止挫败,实际上可能 增加 被破坏的可能性(以防万一) errors.err 由多个目标使用)。如果你用过 program.err 和使用 %.x %.err: %.c 那么你实际上会告诉make该规则同时生成两个文件,它实际上对这种用法是安全的。 - Etan Reisner


一种合理且可移植的方法是重构构建作业以使用文件而不是管道。例如:

foo:
    gcc >$@.log
    grep success $@.log
    cat $@.log
    rm $@.log

显示后删除日志文件显然没有必要;这只是一个通用模板。牛肉是重新定向以取代管道。你甚至可以将它重构为多个食谱:

foo: foo.tmp foo.log
    grep success $@.log
    mv $< $@
%.tmp %.log:
    gcc -o $*.tmp >$*.log

正确清理临时人工制品并对其进行管理是这种方法的明显缺点。


0
2018-06-11 12:37



正如我在另一个答案中评论的那样, foo.tmp foo.log: 实际上创建了两个具有相同配方的规则,因此配方将在您呈现的场景中运行两次。 - Palec
@Palec:谢谢观察。我试图将配方更改为模式规则,但我不确定它是否会解决这个问题;不在我可以测试的地方。 - tripleee
修复了复制粘贴错误。尚未测试,但这应该按预期工作。 - Palec