Golang中os/exec在Windows下的StdinPipe问题解析
Golang中os/exec在Windows下的StdinPipe问题解析
您使用的是哪个Go版本(go version)?
1.18.2
这个问题在最新版本中是否复现?
package main
import (
"fmt"
"io"
"log"
"os/exec"
"os"
)
func main() {
command :="openconnect"
param := []string{"https://192.168.0.100:1443","-u","test","--passwd-on-stdin"}
cmd :=exec.Command(command,param...)
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "123\n")
}()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Start(); err != nil { //Use start, not run
fmt.Println("An error occured: ", err) //replace with logger, or anything you want
}
cmd.Wait()
}
- StdinPipe 不工作
您使用的是哪个操作系统和处理器架构(go env)?
windows
您做了什么?
我遇到了同样的问题。我的代码与作者的代码基本相同。
- 对于用golang编写的命令行程序,stdinpipe可以正常工作。
- BeamMP-Server.exe,stdinpipe无法正常工作,但如果我手动启动它,我可以正常输入命令。 https://beammp.com/ 下载服务器
您期望看到什么?
stdinpipe 对 BeamMP-Server.exe 程序正常工作。
您实际看到了什么?
- 没有错误
- 命令不工作
- 程序的标准输出没有打印所有内容
更多关于Golang中os/exec在Windows下的StdinPipe问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中os/exec在Windows下的StdinPipe问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Windows下使用os/exec.StdinPipe()确实存在一些特殊问题,特别是与某些控制台应用程序交互时。主要原因是Windows控制台程序的输入处理方式与Unix-like系统不同。
问题分析
Windows控制台程序通常期望从控制台输入缓冲区读取,而不是标准输入管道。当使用StdinPipe()时,程序可能无法正确接收输入。
解决方案
方法1:使用syscall设置输入模式(推荐)
package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"syscall"
"unsafe"
)
const (
ENABLE_ECHO_INPUT = 0x0004
ENABLE_LINE_INPUT = 0x0002
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
)
func setConsoleMode(h uintptr, mode uint32) error {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
setConsoleModeProc := kernel32.NewProc("SetConsoleMode")
r, _, err := setConsoleModeProc.Call(h, uintptr(mode))
if r == 0 {
return err
}
return nil
}
func main() {
command := "openconnect"
param := []string{"https://192.168.0.100:1443", "-u", "test", "--passwd-on-stdin"}
cmd := exec.Command(command, param...)
// 为Windows设置额外的属性
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_CONSOLE,
}
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
// 尝试多次写入,确保数据被接收
io.WriteString(stdin, "123\n")
io.WriteString(stdin, "\n") // 额外的换行确保输入被处理
}()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Start(); err != nil {
fmt.Println("启动错误: ", err)
}
cmd.Wait()
}
方法2:使用匿名管道和手动处理
package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
command := "BeamMP-Server.exe"
param := []string{}
cmd := exec.Command(command, param...)
// 创建匿名管道
stdinPipe, stdinWriter, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
cmd.Stdin = stdinPipe
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 设置控制台属性
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_CONSOLE,
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// 等待程序启动
time.Sleep(2 * time.Second)
// 写入数据
_, err = io.WriteString(stdinWriter, "123\n")
if err != nil {
log.Printf("写入错误: %v", err)
}
// 关闭写入端
stdinWriter.Close()
cmd.Wait()
}
方法3:使用conpty虚拟终端(Windows 10+)
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"time"
"github.com/ActiveState/termtest/conpty"
)
func main() {
command := "BeamMP-Server.exe"
// 使用conpty创建虚拟终端
cpty, err := conpty.New(80, 25)
if err != nil {
log.Fatal(err)
}
defer cpty.Close()
// 启动进程
cmd := exec.Command(command)
pid, err := cpty.Spawn(cmd.Path, cmd.Args, &syscall.ProcAttr{})
if err != nil {
log.Fatal(err)
}
// 读取输出
go func() {
buf := make([]byte, 1024)
for {
n, err := cpty.OutPipe().Read(buf)
if err != nil {
return
}
os.Stdout.Write(buf[:n])
}
}()
// 等待并发送输入
time.Sleep(2 * time.Second)
// 发送密码
_, err = cpty.Write([]byte("123\n"))
if err != nil {
log.Printf("写入错误: %v", err)
}
// 等待进程结束
process, _ := os.FindProcess(pid)
process.Wait()
}
方法4:使用go-ansi库处理Windows控制台
package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"github.com/k0kubun/go-ansi"
)
func main() {
command := "BeamMP-Server.exe"
cmd := exec.Command(command)
// 使用ansi包装器
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
// 创建ANSI兼容的写入器
ansiWriter := ansi.NewAnsiStdin(stdin)
go func() {
defer stdin.Close()
// 使用ANSI序列确保输入被处理
io.WriteString(ansiWriter, "123\r\n")
}()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
fmt.Println("启动错误:", err)
}
cmd.Wait()
}
关键点
- Windows控制台程序:许多Windows控制台程序期望从控制台输入缓冲区读取,而不是标准输入管道
- 换行符处理:Windows使用
\r\n,而Unix使用\n - 缓冲问题:可能需要刷新缓冲区或发送额外字符
- 程序类型:检查目标程序是GUI程序还是真正的控制台程序
对于BeamMP-Server.exe这类程序,建议使用方法1或方法2,并确保:
- 使用
syscall.CREATE_NEW_CONSOLE标志 - 正确处理换行符(
\r\n) - 添加适当的延迟确保程序已初始化完成

