【问题标题】:Best way to handle pipes and their exit status in a makefile在 makefile 中处理管道及其退出状态的最佳方法
【发布时间】:2014-08-14 11:18:48
【问题描述】:

如果make中的命令失败,例如gcc,则退出...

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

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

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


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

【问题讨论】:

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

标签: linux bash makefile


【解决方案1】:

您应该能够告诉 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 是 启用,管道的返回状态是最后的值 (最右边)命令以非零状态退出,如果全部退出则为零 命令成功退出。如果保留字!先于一个 管道,该管道的退出状态是逻辑否定 如上所述的退出状态。 shell 等待所有命令 在返回值之前在管道中终止。

【讨论】:

  • 这很好用。我确实需要-c 标志。我想这里要注意的一件事是gcc | grep 之类的东西可能与 grep 有问题,如果什么也没找到,会导致失败。
  • 如果只需要 make 中的一行,set -o pipefail ; gcc | cat 将是等效的。
  • 是的,任何全局修改默认状态的东西都会产生副作用。
  • 我个人认为在 makefile 规则上下文中使用 gcc | grep 的任何东西都已经走错了方向。
【解决方案2】:

我会选择pipefail。但如果你真的不想(如果你只想在第一个进程上失败——而不是在管道其余部分失败的情况下):

SHELL=bash

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

与@jozxyqk self answer相比,唯一的优势是您不会丢失退出状态代码。

【讨论】:

    【解决方案3】:

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

    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
    

    正确清理临时工件并对其进行一般管理是这种方法的一个明显缺点。

    【讨论】:

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

    只需在您的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
    

    【讨论】:

    • 当构建是并行的(例如,Make 被称为make -j),errors.err 将被破坏。票数最高的答案已经给出了使用 Bash 的 pipefail 的建议。
    • Sylvain 的回答建议使用pipefail,但使用SHELL=bash。设置它而不是使用setopt 需要一些知识。无论如何,我将编辑我的答案以指出make 并行调用的问题。谢谢。
    • program.x errors.err: program.c 是否 not 告诉 make 该命令同时生成两个输出。它告诉 make 该命令可用于生成这些文件中的任何一个(但它们都是由它自己制作的,因此多次运行它是正确的)。
    • 嗨@EtanReisner,请发送评论。现在,我并没有说它同时生成两者,只是在我的测试中它防止了崩溃。无论如何,我很欣赏对反对票的评论。没有它,很难理解可能出了什么问题。如果你还有什么要分享的,欢迎光临。干杯。
    • 我的意思是它并不能阻止破坏,实际上可能增加它被破坏的机会(如果errors.err 被多个目标使用)。如果您使用了program.err%.x %.err: %.c,那么您实际上会告诉make 该规则同时生成两个文件,并且对于这种用法实际上是安全的。
    猜你喜欢
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    相关资源
    最近更新 更多