如何捕获有序的 STDOUT/STDERR 并添加时间戳/前缀?

Dei*_*m0s 28 shell bash pipe io-redirection

我已经探索了几乎所有 可用的 类似 问题,但无济于事。

让我详细描述一下这个问题:

我运行一些无人值守的脚本,这些脚本可以生成标准输出和标准错误行,我想按照终端模拟器显示的精确顺序捕获它们,然后向它们添加前缀,如“STDERR:”和“STDOUT:”。

我曾尝试对它们使用管道甚至基于 epoll 的方法,但无济于事。我认为解决方案是在 pty 使用中,尽管我不是这方面的高手。我还查看了Gnome 的 VTE的源代码,但这并没有多大成效。

理想情况下,我会使用Go而不是 Bash 来完成此操作,但我无法做到。由于缓冲,管道似乎自动禁止保持正确的行顺序。

有人能做类似的事情吗?或者这是不可能的?我认为,如果终端模拟器可以做到,那么它就不是——也许通过创建一个以不同方式处理 PTY 的小型 C 程序?

理想情况下,我会使用异步输入来读取这 2 个流(STDOUT 和 STDERR),然后根据我的需要重新打印它们,但输入顺序至关重要!

注意:我知道stderred但它不适用于 Bash 脚本,并且无法轻松编辑以添加前缀(因为它基本上包含了大量系统调用)。

更新:在两个要点下面添加

(可以在我提供的示例脚本中添加亚秒随机延迟,以证明结果一致)

更新:正如@Gilles 指出的那样,这个问题的解决方案也将解决另一个问题。但是,我得出的结论是,不可能在这里和那里做所要求的事情。当使用2>&1两个流在 pty/pipe 级别正确合并时,但要单独使用流并以正确的顺序使用,确实应该使用涉及系统调用挂钩的stderred方法,并且可以在许多方面被视为

如果有人可以反驳上述内容,我将渴望更新这个问题。

pet*_*rph 14

您可能会使用协进程。将给定命令的两个输出提供给两个sed实例(一个用于stderr另一个 for stdout)的简单包装器,它们进行标记。

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"
Run Code Online (Sandbox Code Playgroud)

注意几点:

  1. 对于许多人(包括我)来说,这是一个神奇的咒语 - 出于某种原因(请参阅下面的链接答案)。

  2. 不能保证它不会偶尔交换几行 - 这一切都取决于协进程的调度。实际上,几乎可以保证在某个时间点它会。也就是说,如果保持严格的顺序相同的,你必须处理来自这两个数据stderrstdin在同一个进程,否则内核调度可以(而且会)做它的一个烂摊子。

    如果我正确理解了这个问题,则意味着您需要指示 shell 将两个流重定向到一个进程(这可以在 AFAIK 中完成)。当该进程开始决定首先对什么采取行动时,麻烦就开始了——它必须轮询两个数据源,并在某个时候进入处理一个流的状态,并且数据在完成之前到达两个流。这正是它崩溃的地方。这也意味着,像这样包装输出系统调用stderred可能是实现您想要的结果的唯一方法(即使这样,一旦某些东西在多处理器系统上变成多线程,您可能会遇到问题)。

至于协进程,请务必阅读 Stéphane 在How do you use the command coproc in Bash 中的出色回答深入了解。


slm*_*slm 7

方法#1。使用文件描述符和 awk

使用此题为“是否有 Unix 实用程序将时间戳添加到文本行”中的解决方案,这样的事情怎么样并且这个问答题为:将STDOUT 和 STDERR 管道到 shell 脚本中的两个不同进程?.

该方法

第 1 步,我们在 Bash 中创建 2 个函数,它们将在调用时执行时间戳消息:

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
Run Code Online (Sandbox Code Playgroud)

第 2 步,您将使用上述函数来获得所需的消息:

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut
Run Code Online (Sandbox Code Playgroud)

例子

在这里,我编造了一个示例,该示例将写入aSTDOUT,休眠 10 秒,然后将输出写入 STDERR。当我们将此命令序列放入我们上面的构造中时,我们会收到您指定的消息。

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b
Run Code Online (Sandbox Code Playgroud)

方法#2。使用注释输出

有一个名为的工具annotate-output,它是devscripts软件包的一部分,可以执行您想要的操作。唯一的限制是它必须为您运行脚本。

例子

如果我们将上面的示例命令序列放入一个脚本中,mycmds.bash如下所示:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b
Run Code Online (Sandbox Code Playgroud)

然后我们可以像这样运行它:

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0
Run Code Online (Sandbox Code Playgroud)

可以针对时间戳部分控制输出格式,但不能超出该部分。但它与您正在寻找的输出相似,因此它可能符合要求。