进程重定向可以在bash中同步吗

Can process redirection be synchronised in bash?

本文关键字:同步 bash 重定向 进程      更新时间:2023-10-16

我有以下用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的字符串之前。

  1. 为什么会出现上述情况?例如,bash是否从右到左评估所有流程替换
  2. 有没有任何方法可以同步这些字符串,以便字符串的顺序与C++示例程序中定义的顺序相同
让你烦恼的行为是glibc的。在Linux中,链接到glibc的程序;这几乎是所有的东西—根据stdout和stderr的重定向位置,改变它们的输出缓冲模式。如果它们连接到TTY,则它们是行缓冲的。如果它们被重定向到文件、管道或其他非TTY设备,glibc会将它们切换到完全缓冲模式。在全缓冲模式下,输出仅每隔4KB左右刷新一次

在命令行中,这会影响main.outsed。添加>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.