我有以下用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)
如果Icat main.log
,结果为:
ERROR : Error 1
ERROR : Wowza... that's bad...
ERROR : Caused by X.
SUCCESS: Ok
SUCCESS: All good in the end.
正如您所看到的,发送到STDERR的字符串都出现在发送到STDOUT的字符串之前。
- 为什么会出现上述情况?例如,bash是否从右到左评估所有流程替换
- 有没有任何方法可以同步这些字符串,以便字符串的顺序与C++示例程序中定义的顺序相同
在命令行中,这会影响main.out
和sed
。添加>
和2>
重定向时,main.out
的stdout和stderr将得到完全缓冲。两个sed
的stdout流被完全缓冲,因为它们被重定向到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
进程。它可以对两个CCD_ 16进程进行交织。它可以随心所欲地执行上下文切换。
更不用说,在多核或多处理器系统上,进程可以同时运行。这是一个真正的种族。无论哪个sed
运行得最快,都将首先输出。
要同步处理,您必须去掉多个sed
进程。相反,让一个进程从两个流中读取。这是一个更复杂的设计。您需要两个输入描述符,而不仅仅是一个。您需要以某种方式select()
,并且只有在有可用输入的情况下才能从中读取。这种多路复用需要一些高级的shell脚本。你最好用另一种语言来做这件事。
我相信我自己可以通过演示回答1。
正如我所假设的,过程替换从右到左发生。
例如,执行./main.out 2> >(sed "s/^/ERROR : /g" >> main.log) > >(sed "s/^/SUCCESS: /g" >> 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.