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)
注意几点:
对于许多人(包括我)来说,这是一个神奇的咒语 - 出于某种原因(请参阅下面的链接答案)。
不能保证它不会偶尔交换几行 - 这一切都取决于协进程的调度。实际上,几乎可以保证在某个时间点它会。也就是说,如果保持严格的顺序相同的,你必须处理来自这两个数据stderr
与stdin
在同一个进程,否则内核调度可以(而且会)做它的一个烂摊子。
如果我正确理解了这个问题,则意味着您需要指示 shell 将两个流重定向到一个进程(这可以在 AFAIK 中完成)。当该进程开始决定首先对什么采取行动时,麻烦就开始了——它必须轮询两个数据源,并在某个时候进入处理一个流的状态,并且数据在完成之前到达两个流。这正是它崩溃的地方。这也意味着,像这样包装输出系统调用stderred
可能是实现您想要的结果的唯一方法(即使这样,一旦某些东西在多处理器系统上变成多线程,您可能会遇到问题)。
至于协进程,请务必阅读 Stéphane 在How do you use the command coproc in Bash 中的出色回答?深入了解。
使用此题为“是否有 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)
在这里,我编造了一个示例,该示例将写入a
STDOUT,休眠 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)
有一个名为的工具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)
可以针对时间戳部分控制输出格式,但不能超出该部分。但它与您正在寻找的输出相似,因此它可能符合要求。