如何清除在 Golang 中 exec.Command 中设置为 StdOut 的 bytes.Buffer(b.Reset 不起作用)
How to clear a bytes.Buffer that is set as StdOut in exec.Command in Golang (b.Reset is not working)
我正在尝试在 GoLang 中编写一个命令行应用程序包装器,它公开一个 REST API 以与所述应用程序交互并显示其输出。
基本上当 REST API 服务器启动时,它应该启动一个保持 运行ning 的命令行应用程序(例如 Powershell.exe) 并且应该能够与应用程序交互并在通过 API 请求时显示其输出。
到目前为止,服务器已启动,子进程也已启动,我可以向该进程发出命令并检索其输出。为了捕获进程的输出,我使用了一个 bytes.Buffer,我附加到进程 Stdout 和 标准错误.
问题是那个输出缓冲区似乎变得无限大,而且没有办法清除它。 运行 该缓冲区上的“.Reset()”似乎被完全忽略,并且始终显示完整的缓冲输出。
代码如下:
package main
import (
"bytes"
"fmt"
"log"
"net/http"
"os/exec"
)
func main() {
var c = exec.Command("powershell.exe")
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
stdin, err := c.StdinPipe()
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Do stuff")
})
http.HandleFunc("/clear", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Cleared")
b.Reset()
})
http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
stdin.Write([]byte("echo hi\n"))
fmt.Fprintf(w, "OK")
})
http.HandleFunc("/show", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, string(b.Bytes()))
})
fmt.Println("SERVER 1 STARTING")
c.Start()
c.CombinedOutput()
http.ListenAndServe(":8081", nil)
fmt.Println("SERVER 1 STOPPED")
}
所以这里的预期行为是,当您调用“/write”时,它将在子应用程序中执行 "echo hi"。 “/show”将显示应用程序的输出缓冲区。和“/clear”应该清除输出缓冲区,这样当你运行“/write”,然后“/clear”,然后是“/show”,应该显示一个空字符串。但在这种情况下,它会一直显示永远不会被清除的应用程序输出。
有趣的是,如果我通过 b.WriteString() 手动写入缓冲区,然后执行 b.Reset(),该内容确实被删除,但不是来自命令行应用程序的内容。
当 bytes.Buffer 附加到 Stdout/Stderr 时,有什么方法可以正确清除它吗?或者,是否有某种方法可以将缓冲区限制为特定行数?
@JimB 是正确的,这似乎是一个同步问题。这不是网络服务器,而是 exec.Command 的工作方式,以及缓冲区的共享方式。
这个程序可能有助于解释。它每秒运行 ping 并读取缓冲区,每次都尝试重置缓冲区。 Ping 每秒都在写入缓冲区。
func main() {
cmd := exec.Command("ping", "google.com")
var buf bytes.Buffer
cmd.Stdout = &buf
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
for i := 0; i < 3; i++ {
time.Sleep(1 * time.Second)
log.Println(buf.String())
buf.Reset()
}
cmd.Process.Kill()
}
但是每次输出都是累加的,好像忽略了对buf.Reset()调用的调用:
$ go run main.go
2020/01/28 22:05:25 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms
2020/01/28 22:05:26 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=52.0 ms
2020/01/28 22:05:27 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=52.0 ms
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=3 ttl=50 time=57.1 ms
bytes.Buffer 可以替换为同步缓冲区,其中写入和读取受互斥锁保护。这有点简单,但是例如:
type SyncBuf struct {
mu sync.Mutex
buf bytes.Buffer
}
func (s *SyncBuf) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.buf.Write(p)
}
func (s *SyncBuf) Reset() {
s.mu.Lock()
defer s.mu.Unlock()
s.buf.Reset()
}
func (s *SyncBuf) String() string {
s.mu.Lock()
defer s.mu.Unlock()
return s.buf.String()
}
替换缓冲区后,输出行为符合预期,与 Reset() 调用有关:
$ go run main.go
2020/01/28 22:07:03 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes
64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.4 ms
2020/01/28 22:07:04 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=51.8 ms
2020/01/28 22:07:05 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=3 ttl=50 time=50.8 ms
在操场上看到 full example。
免责声明:这是作为插图发布的,可能无法很好地解决您的问题。
我正在尝试在 GoLang 中编写一个命令行应用程序包装器,它公开一个 REST API 以与所述应用程序交互并显示其输出。
基本上当 REST API 服务器启动时,它应该启动一个保持 运行ning 的命令行应用程序(例如 Powershell.exe) 并且应该能够与应用程序交互并在通过 API 请求时显示其输出。
到目前为止,服务器已启动,子进程也已启动,我可以向该进程发出命令并检索其输出。为了捕获进程的输出,我使用了一个 bytes.Buffer,我附加到进程 Stdout 和 标准错误.
问题是那个输出缓冲区似乎变得无限大,而且没有办法清除它。 运行 该缓冲区上的“.Reset()”似乎被完全忽略,并且始终显示完整的缓冲输出。
代码如下:
package main
import (
"bytes"
"fmt"
"log"
"net/http"
"os/exec"
)
func main() {
var c = exec.Command("powershell.exe")
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
stdin, err := c.StdinPipe()
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Do stuff")
})
http.HandleFunc("/clear", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Cleared")
b.Reset()
})
http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
stdin.Write([]byte("echo hi\n"))
fmt.Fprintf(w, "OK")
})
http.HandleFunc("/show", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, string(b.Bytes()))
})
fmt.Println("SERVER 1 STARTING")
c.Start()
c.CombinedOutput()
http.ListenAndServe(":8081", nil)
fmt.Println("SERVER 1 STOPPED")
}
所以这里的预期行为是,当您调用“/write”时,它将在子应用程序中执行 "echo hi"。 “/show”将显示应用程序的输出缓冲区。和“/clear”应该清除输出缓冲区,这样当你运行“/write”,然后“/clear”,然后是“/show”,应该显示一个空字符串。但在这种情况下,它会一直显示永远不会被清除的应用程序输出。
有趣的是,如果我通过 b.WriteString() 手动写入缓冲区,然后执行 b.Reset(),该内容确实被删除,但不是来自命令行应用程序的内容。
当 bytes.Buffer 附加到 Stdout/Stderr 时,有什么方法可以正确清除它吗?或者,是否有某种方法可以将缓冲区限制为特定行数?
@JimB 是正确的,这似乎是一个同步问题。这不是网络服务器,而是 exec.Command 的工作方式,以及缓冲区的共享方式。
这个程序可能有助于解释。它每秒运行 ping 并读取缓冲区,每次都尝试重置缓冲区。 Ping 每秒都在写入缓冲区。
func main() {
cmd := exec.Command("ping", "google.com")
var buf bytes.Buffer
cmd.Stdout = &buf
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
for i := 0; i < 3; i++ {
time.Sleep(1 * time.Second)
log.Println(buf.String())
buf.Reset()
}
cmd.Process.Kill()
}
但是每次输出都是累加的,好像忽略了对buf.Reset()调用的调用:
$ go run main.go 2020/01/28 22:05:25 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms 2020/01/28 22:05:26 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=52.0 ms 2020/01/28 22:05:27 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.3 ms 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=52.0 ms 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=3 ttl=50 time=57.1 ms
bytes.Buffer 可以替换为同步缓冲区,其中写入和读取受互斥锁保护。这有点简单,但是例如:
type SyncBuf struct {
mu sync.Mutex
buf bytes.Buffer
}
func (s *SyncBuf) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.buf.Write(p)
}
func (s *SyncBuf) Reset() {
s.mu.Lock()
defer s.mu.Unlock()
s.buf.Reset()
}
func (s *SyncBuf) String() string {
s.mu.Lock()
defer s.mu.Unlock()
return s.buf.String()
}
替换缓冲区后,输出行为符合预期,与 Reset() 调用有关:
$ go run main.go 2020/01/28 22:07:03 PING google.com(syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e)) 56 data bytes 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=1 ttl=50 time=50.4 ms 2020/01/28 22:07:04 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=2 ttl=50 time=51.8 ms 2020/01/28 22:07:05 64 bytes from syd09s13-in-x0e.1e100.net (2404:6800:4006:807::200e): icmp_seq=3 ttl=50 time=50.8 ms
在操场上看到 full example。
免责声明:这是作为插图发布的,可能无法很好地解决您的问题。