【问题标题】:Can process redirection be synchronised in bash?进程重定向可以在 bash 中同步吗?
【发布时间】:2014-02-25 22:54:43
【问题描述】:

我有以下使用gcc -lstdc++ main.cpp -o main.out 编译的 C++ 程序。

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main(int argc, char** argv) {
    cerr << "Error 1" << endl;
    cout << "Ok "  << endl;
    cerr << "Wowza... that's bad..."  << endl;
    cerr << "Caused by X.";
    cout << "All good in the end." << endl;

    return 0;
};

我还有一个如下的 bash 脚本,其主要目的是在 STDOUT 前加上“SUCCESS:”,在 STDERR 前加上“ERROR:”。

./main.out > >(sed "s/^/SUCCESS: /g" >> main.log) 2> >(sed "s/^/ERROR  : /g" >> main.log)

如果我cat main.log 结果是:

ERROR  : Error 1
ERROR  : Wowza... that's bad...
ERROR  : Caused by X.
SUCCESS: Ok 
SUCCESS: All good in the end.

如您所见,发送到 STDERR 的字符串都出现在发送到 STDOUT 的字符串之前。

  1. 为什么会出现上述情况?例如,bash 是否从右到左评估所有进程替换?
  2. 有什么方法可以同步这些,使字符串的顺序是 C++ 示例程序中定义的?

【问题讨论】:

    标签: c++ bash process


    【解决方案1】:

    困扰你的行为是 glibc 的。在 Linux 中,链接到 glibc 的程序(几乎是所有这些程序)vary their output buffering mode 基于 stdout 和 stderr 的重定向位置。如果它们连接到 TTY,它们是行缓冲的。如果它们被重定向到文件、管道或其他非 TTY 设备,glibc 会将它们切换到 完全缓冲 模式。在完全缓冲模式下,输出仅每 4KB 左右刷新一次。

    在您的命令行中,这会影响main.outsed。当您添加 &gt;2&gt; 重定向时,main.out 的 stdout 和 stderr 被完全缓冲。两个seds 的标准输出流被完全缓冲,因为它们被重定向到main.log

    您可以使用stdbuf 来覆盖此行为。 stdbuf 运行带有您选择的输入和输出缓冲的命令。它适用于大多数程序。

    如果您将stdbuf 覆盖添加到三个命令中的每一个,您可以让它们交错输出。这是个好消息。

    $ rm main.log; stdbuf -oL -eL ./main.out > >(stdbuf -oL sed "s/^/SUCCESS: /g" >> main.log) 2> >(stdbuf -oL sed "s/^/ERROR  : /g" >> main.log); cat main.log
    ERROR  : Error 1
    ERROR  : Wowza... that's bad...
    SUCCESS: Ok 
    SUCCESS: All good in the end.
    ERROR  : Caused by X.
    
    $ rm main.log; stdbuf -oL -eL ./main.out > >(stdbuf -oL sed "s/^/SUCCESS: /g" >> main.log) 2> >(stdbuf -oL sed "s/^/ERROR  : /g" >> main.log); cat main.log
    SUCCESS: Ok 
    SUCCESS: All good in the end.
    ERROR  : Error 1
    ERROR  : Wowza... that's bad...
    ERROR  : Caused by X.
    
    $ rm main.log; stdbuf -oL -eL ./main.out > >(stdbuf -oL sed "s/^/SUCCESS: /g" >> main.log) 2> >(stdbuf -oL sed "s/^/ERROR  : /g" >> main.log); cat main.log
    SUCCESS: Ok 
    ERROR  : Error 1
    SUCCESS: All good in the end.
    ERROR  : Wowza... that's bad...
    ERROR  : Caused by X.
    

    坏消息是行的顺序是不可预测的。仍然不能保证输出将按照您的程序编写的顺序。

    原因是从根本上来说,这里有一个竞态条件。您的程序和两个sed 命令是三个独立的进程。无法保证它们会按特定顺序运行,即当您的程序向 stdout 输出一行时,Linux 会将控制权切换到适当的 sed 进程,然后切换回您的程序。

    Linux 可以允许您的程序写入其所有输出,然后将控制权切换到sed 进程。它可以交错两个sed 进程。它可以随心所欲地执行上下文切换。

    更不用说,在多核或多处理器系统上,进程可以同时运行。这是一场真正的比赛。 sed 运行速度最快的将首先输出。

    同步处理,您必须摆脱多个sed 进程。相反,让一个进程从两个流中读取。这是一个更复杂的设计。您将需要两个输入描述符,而不仅仅是一个。您需要以某种方式 select() 并且只有在有可用输入时才从它们读取。这种多路复用需要一些高级的 shell 脚本。使用另一种语言可能会更好。

    【讨论】:

    • 坏消息是行的交错是随机的(正如您的三个示例所证明的那样,使用相同的命令但输出不同)?
    • @user1420752 是的。有效随机。如果您想让我详细说明我的最后一段,请告诉我。我需要一些时间来为此创建一个示例。
    • 我只能说……你是个传奇。我认为自己在 Linux 方面相当熟练,但你的答案是正确的。我希望有一种更简单的方法来完成我想要的,例如,如果 bash 能够使用 &gt;() 语法在进程之间交替。我也试过rm main.log; ./main.out &gt; &gt;(sed "s/^/$(date +%s.%N) SUCCESS: /g" &gt;&gt; main.log) 2&gt; &gt;(sed "s/^/$(date +%s.%N) ERROR : /g" &gt;&gt; main.log); cat main.log ; sync ; sort -nk1 main.log,但程序执行速度太快(或者计时器分辨率太不准确),以至于每个进程重定向只有一个时间值。
    • 我之前的评论,我没有考虑清楚 - 只有一个时间值,因为 $(date +%s.%N) 只被评估一次。
    【解决方案2】:

    我相信我可以通过演示自己回答 1。

    正如我假设的那样,进程替换是从右到左发生的。

    例如,执行./main.out 2&gt; &gt;(sed "s/^/ERROR : /g" &gt;&gt; main.log) &gt; &gt;(sed "s/^/SUCCESS: /g" &gt;&gt; main.log) ; cat main.log 会在 STDERR 消息之前产生所有 STDOUT 消息:

    SUCCESS: Ok 
    SUCCESS: All good in the end.
    ERROR  : Error 1
    ERROR  : Wowza... that's bad...
    ERROR  : Caused by X.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 1970-01-01
      • 2018-11-21
      • 2013-09-27
      相关资源
      最近更新 更多