【问题标题】:Have `make` echo to standard error without re-direction?在没有重定向的情况下让“make”回显到标准错误?
【发布时间】:2016-05-18 11:30:05
【问题描述】:

我的Makefile 中的一些目标运行我感兴趣的程序的输出(它们发送到stdout)。出于我不知道的原因,make 的作者决定回显执行的命令到stdout,污染后者。

建议here 提出一个解决这个涉及交换文件描述符的困难方法。我想知道是否有更简单的方法将make 回显到stderr

我浏览了makeman 页面,但除了-s 选项外,没有找到任何相关内容。我更喜欢保留命令的回声,但在stderr 中保留它。

我还尝试制作一个辅助目标(我将其作为所有其他目标的先决条件),我将其放入:

exec 3>&2
exec 2>&1
exec 1>&3

但是 bash 抱怨 3 不是一个有效的文件描述符。我只试过exec 1>&2,但没有任何效果...

【问题讨论】:

  • 即使你这样做了,你的终端仍然会被make的输出“污染”。
  • 我想从一个脚本调用make,然后它会处理上述程序的输出。另外,我只需将stderr 重定向到一个文件,如果需要,我可以稍后查看。

标签: bash makefile


【解决方案1】:

make 在 stdout 上显示命令行的原因是因为这是 make 的 POSIX 标准所要求的,并且是 30 多年历史所期望的。请参阅http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html 并搜索“STDOUT”部分。

您不能在配方中修改 make 程序中的文件描述符,因为配方在子外壳中运行:您对文件描述符所做的任何更改仅在子外壳中生效。在 UNIX 中,子进程无法修改其父进程的文件描述符。

同样,make 中配方中的每一行 都在一个不同 子shell 中运行。如果你想做一些花哨的事情,比如为食谱重定向输出,你必须把它全部写在一行上:

exec 3>&2; exec 2>&1; exec 1>&3; <my command here>

当然,如果您打算经常这样做,我会将其放入 make 变量中并使用该变量。

没有办法让 make 将其输出写入 stderr 而不是 stdout,除非您想修改 GNU make 的源代码并改用您自己构建的版本。只要您使用较新版本的 GNU make(4.0 及更高版本),这样做实际上很简单,因为所有输出都是从一个 plase 生成的(在 output.c 中)。

【讨论】:

  • 谢谢。这很有帮助!
  • "没有办法让 make 将其输出写入 stderr 而不是 stdout。"原来有:)看我的回答。
  • 不,make 仍在将其输出写入标准输出。您刚刚将其标准输出重定向到不同的文件描述符。
【解决方案2】:

如果你不想污染 make 产生的 echo 的输出,你不能简单地运行

make -n >&2 && make -s

这是示例 Makefile:

all:
    ls
    echo done

这是make的输出:

ls
Makefile
echo done
done

这是make -n &gt;&amp;2 &amp;&amp; make -s的输出:

ls
echo done
Makefile
done

当然,任一步骤的输出都可以重定向到文件。

【讨论】:

  • 这样我就不会知道哪个命令产生了哪个输出。特别是对于实际进行编译的target,如果出现编译错误,我将不知道是哪些编译命令导致了错误。对于多次调用 make 并追溯分析 stderr 的脚本来说,这可能是一个严重的问题。
  • 请注意,stdout 和 stderr 无论如何都不会同步。因此,如果您看到 ls\necho abc\necho def - 您无法确定第二行是 ls 的结果还是单独执行。
【解决方案3】:

你完全可以在Makefile 中做的是:

define REDIR
@printf 1>&2 "%s\n" '$(1)'; $(1)
endef

.PHONY: all
all:
        $(call REDIR,echo updating .stamp)
        $(call REDIR,touch .stamp)

也就是说,通过宏控制回显自己的命令。不幸的是,它涉及将您的配方行编写为 `$(call ...) 语法。

REDIR 现在通过宏扩展实现了回显命令并执行它的语义。

1&gt;&amp;2 是 Bash 特定的语法,用于将标准错误文件描述符复制到标准输出,因此该命令有效地打印到标准输出。

试运行:

$ make
echo updating .stamp
updating .stamp
touch .stamp

$ make 2> /dev/null
updating .stamp

如您所见,updating .stamp 是我们显式编码的echo 行的输出,很好地转到标准输出。命令被隐式发送到标准错误。

【讨论】:

    【解决方案4】:

    假设我们有以下Makefile

    target-1:
        target-1-body
    target-2:
        target-2-body
    target-3:
        target-3-body
    

    我们修改如下:

    target-1-raw:
        target-1-body
    target-2-raw:
        target-2-body
    target-3-raw:
        target-3-body
    
    %-raw:
        echo "Error: Target $@ does not exist!"    
    %:
        @make $@-raw 3>&2 2>&1 1>&3
    

    调用与以前相同,例如make target-1.

    通过两个额外的目标,我们将make 输出到stderr

    仅供参考:我是trying,负责进一步开发此解决方案,以便用户无法直接调用原始目标。

    【讨论】:

    • 首先你应该在调用子make时总是使用$(MAKE),而不是原始的make。其次,这只适用于“简单”的目标;如果你有 .o 文件依赖于 .h 和 .cpp 文件,以及 .a 文件依赖于 .o 文件和依赖于上述所有文件的二进制文件等等。你是否要重写你的 makefile 以便它们都使用这些原始文件内部目标?最后,您正在为每个目标调用一个全新的 make 实例:如果您有很多目标,这将显着减慢您的构建速度。
    • @MadScientist 1.make$(MAKE) 有什么区别? 2. 是的,所有的目标和依赖都需要重命名。我同意@Kaz 建议的更改更容易自动化。 3. 不,目标依赖项是原始目标,因此只运行了一个额外的 make 实例。
    • @AlexCohn 您使用的是哪个版本的make?我检查了东西是继承的,包括变量和命令行参数(尝试了-s 和在命令行上设置变量)。使用版本 3.81。
    • @AlexCohn 好的。但是你的最后一句话(“任何命令行参数”)太强了。
    【解决方案5】:

    另一个 posix 不兼容的解决方案是放置

    #!/bin/bash
    exec 3>&2; exec 2>&1; exec 1>&3;
    

    相对于我的项目进入helper/stderr,并且

    helperdir = helper
    SHELL = BASH_ENV="$(helperdir)/stderr" /bin/bash
    

    进入我的 Makefile。

    现在所有执行的规则代码输出都被重定向到stderr文件描述符。

    BASH_ENV 环境变量如果设置为脚本路径,则会在每次 bash 调用时执行该脚本。

    【讨论】:

      猜你喜欢
      • 2010-11-18
      • 2019-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-19
      相关资源
      最近更新 更多