为什么从 StdoutPipe 读取会改变输出顺序?
Why does reading from StdoutPipe changes order of output?
我有以下调用命令的函数,但我无法在此处发布。以前我使用 cmd.Stdout = os.Stdout
在我的命令行上获取命令输出,效果很好:
func run(cmd *exec.Cmd) {
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
这给了我以下命令行输出,这很棒:
[loader] loading programm...
[programm] foo bar
现在我需要先与输出交互,所以我尝试使用 StdoutPipe
:
func run(cmd *exec.Cmd) {
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
if err := cmd.Start(); err != nil {
panic(err)
}
reader := bufio.NewReader(stdout)
for {
s, err := reader.ReadString('\n')
if err != nil {
break
}
// do some evaluation of s here
fmt.Print(s)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
但这打乱了消息的顺序,我没有得到以下输出:
[programm] foo bar
[loader] loading programm...
为什么输出不再按顺序排列?如何在使用 stdoutPipe 时保持输出按顺序排列?
编辑:
如果我尝试像这样使用 io.MultiWriter
,也会发生同样的情况:
func run(cmd *exec.Cmd) {
var reader bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &reader)
if err := cmd.Start(); err != nil {
panic(err)
}
for {
s, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Print(s)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
很大程度上取决于您 运行 的程序。
你那个:
I used stdbuf -o0
to deactivate the buffering
这只会影响程序的 stdout
(POSIX 系统上的文件描述符 1),而不影响其 stderr
(文件描述符 2)。但是 POSIX 系统通常使用以下默认值,至少对于 C 语言程序或那些使用 C 运行 时间系统的程序:
- stdin:缓冲不是特别相关,但输入通常会完全缓冲。
- stdout:如果文件描述符 1 是“tty”,则缓冲行;否则完全缓冲。
- stderr:无缓冲。
通过调用 C 库 isatty
函数来确定什么是“tty”,该函数通常使用 termios operation to do this. For instance, the glibc isatty
calls tcgetattr
and verifies that it returns without error. The glibc tcgetattr uses the TCGETATTR
ioctl。所以“tty 设备”是实现 TCGETATTR
.
的设备
过去使用的实际串行线(例如 COM1 和 COM2),或终端集中器设备,例如 中的 DH/DM 或 DZ 旧日,当然实行TCGETATTR
。伪终端也是如此,例如交互式 ssh
会话、script
和其他程序所使用的伪终端。这使得 1970 年代和 1980 年代的所有旧软件能够继续使用虚拟化 ASR33, ADM3A, or DECwriter 继续工作。1
无论如何,这里的关键是管道——或插座或类似物——不实现TCGETATTR
,所以如果 stdout 是一个管道,那么 isatty(1)
就是 0(假)。这使得 stdout 完全缓冲.
请记住,这一切都发生在 您 运行 的程序中。您自己的程序,读取其他程序的输出,(不一定)不是问题。当其他程序实际 发送 时,您的程序仅 获取 输出。在全缓冲模式下,其他程序在填满缓冲区块或调用某些输出刷新操作时仅 发送 输出。
一些程序将输出混合到 stdout——他们通常假设 是行缓冲的,因为编写(有缺陷的)程序的人只用 tty 输出测试它——并输出到标准错误。当 stdout 被重定向到一个文件或管道时,这些程序会表现得很奇怪。像 stdbuf
这样的程序——可以作为库预加载实现,hence might not work on setuid programs——可以用来解决这些问题。
在 Go 中,缓冲更多地取决于您自己的程序。 Go 不使用 POSIX 设置。例如,参见 Idiomatically buffer os.Stdout。
1我喜欢我的虚拟Tek4025,Heathkit H19等,更好,虽然旧的打印终端有话要说!或旧的 AAA 终端,在斜视模式下最多可显示 60 条线路。 OK,Terminal、iterm2等都打不过了
我有以下调用命令的函数,但我无法在此处发布。以前我使用 cmd.Stdout = os.Stdout
在我的命令行上获取命令输出,效果很好:
func run(cmd *exec.Cmd) {
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
这给了我以下命令行输出,这很棒:
[loader] loading programm...
[programm] foo bar
现在我需要先与输出交互,所以我尝试使用 StdoutPipe
:
func run(cmd *exec.Cmd) {
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
if err := cmd.Start(); err != nil {
panic(err)
}
reader := bufio.NewReader(stdout)
for {
s, err := reader.ReadString('\n')
if err != nil {
break
}
// do some evaluation of s here
fmt.Print(s)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
但这打乱了消息的顺序,我没有得到以下输出:
[programm] foo bar
[loader] loading programm...
为什么输出不再按顺序排列?如何在使用 stdoutPipe 时保持输出按顺序排列?
编辑:
如果我尝试像这样使用 io.MultiWriter
,也会发生同样的情况:
func run(cmd *exec.Cmd) {
var reader bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &reader)
if err := cmd.Start(); err != nil {
panic(err)
}
for {
s, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Print(s)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
return
}
很大程度上取决于您 运行 的程序。
你
I used
stdbuf -o0
to deactivate the buffering
这只会影响程序的 stdout
(POSIX 系统上的文件描述符 1),而不影响其 stderr
(文件描述符 2)。但是 POSIX 系统通常使用以下默认值,至少对于 C 语言程序或那些使用 C 运行 时间系统的程序:
- stdin:缓冲不是特别相关,但输入通常会完全缓冲。
- stdout:如果文件描述符 1 是“tty”,则缓冲行;否则完全缓冲。
- stderr:无缓冲。
通过调用 C 库 isatty
函数来确定什么是“tty”,该函数通常使用 termios operation to do this. For instance, the glibc isatty
calls tcgetattr
and verifies that it returns without error. The glibc tcgetattr uses the TCGETATTR
ioctl。所以“tty 设备”是实现 TCGETATTR
.
过去使用的实际串行线(例如 COM1 和 COM2),或终端集中器设备,例如 中的 DH/DM 或 DZ 旧日,当然实行TCGETATTR
。伪终端也是如此,例如交互式 ssh
会话、script
和其他程序所使用的伪终端。这使得 1970 年代和 1980 年代的所有旧软件能够继续使用虚拟化 ASR33, ADM3A, or DECwriter 继续工作。1
无论如何,这里的关键是管道——或插座或类似物——不实现TCGETATTR
,所以如果 stdout 是一个管道,那么 isatty(1)
就是 0(假)。这使得 stdout 完全缓冲.
请记住,这一切都发生在 您 运行 的程序中。您自己的程序,读取其他程序的输出,(不一定)不是问题。当其他程序实际 发送 时,您的程序仅 获取 输出。在全缓冲模式下,其他程序在填满缓冲区块或调用某些输出刷新操作时仅 发送 输出。
一些程序将输出混合到 stdout——他们通常假设 是行缓冲的,因为编写(有缺陷的)程序的人只用 tty 输出测试它——并输出到标准错误。当 stdout 被重定向到一个文件或管道时,这些程序会表现得很奇怪。像 stdbuf
这样的程序——可以作为库预加载实现,hence might not work on setuid programs——可以用来解决这些问题。
在 Go 中,缓冲更多地取决于您自己的程序。 Go 不使用 POSIX 设置。例如,参见 Idiomatically buffer os.Stdout。
1我喜欢我的虚拟Tek4025,Heathkit H19等,更好,虽然旧的打印终端有话要说!或旧的 AAA 终端,在斜视模式下最多可显示 60 条线路。 OK,Terminal、iterm2等都打不过了