Golang程序在其他进程清屏时运行失败问题探讨

Golang程序在其他进程清屏时运行失败问题探讨 为了原型设计,我用C#创建了一个程序,它接收一个数字作为输入参数。 这个数字表示以秒为单位的超时时间。 每秒它会在控制台输出当前秒数。 每过10秒,程序会清空屏幕。

在Golang中,我编写了以下代码,用于调用带有参数20(表示超时时间为20秒)的C#可执行程序:

func ExecuteProcess (path string, parameters []string)(error){
  cmd := exec.Command(path, parameters...)
  fmt.Printf("[ExecuteProcess].Running command and waiting for it to finish...\n")

  output, err := cmd.CombinedOutput()
  if err != nil {
	fmt.Println(fmt.Sprint(err) + ": " + string(output) + "\n")
	return err
  } else {
	fmt.Println(string(output))
  }

  return nil
}

当我启动这个程序时,我看到以下输出:

[ExecuteProcess].运行命令并等待其完成… 退出状态 3762504530: 等待 0 秒 等待 1 秒 等待 2 秒 等待 3 秒 等待 4 秒 等待 5 秒 等待 6 秒 等待 7 秒 等待 8 秒 等待 9 秒 等待 10 秒

未处理的异常:System.IO.IOException: 句柄无效。

位于 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) 位于 System.Console.GetBufferInfo(Boolean throwOnNoConsole, Boolean& succeeded) 位于 System.Console.Clear() 位于 TestExe.Program.Main(String[] args)

退出状态 3762504530程序已完成。

当被调用的程序尝试清空屏幕时,我看到了错误:句柄无效。 您能建议如何修复这个错误吗?并且如何等待程序自行关闭?


更多关于Golang程序在其他进程清屏时运行失败问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

我们可以运行任何其他程序,甚至是批处理脚本。
Golang 不会在用户界面中启动它。
我只能通过进程查看它。
问题是当第三方应用程序清理其进程中的窗口时,Golang 应用程序会崩溃!

更多关于Golang程序在其他进程清屏时运行失败问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当你无法更改想要运行的程序时,需要将其封装在独立的 cmd.exe 实例中,这样你将无法再获取其输出。

欢迎使用 Windows 系统。

无论选择哪种编程语言,只要尝试在 Go 中实现类似功能,都会遇到相同的问题。

据我所知,这是Windows系统的限制。由于C#程序并不"拥有"其运行的命令行环境,因此无法清屏。

如果你的Windows版本足够新,可以尝试使用ANSI转义序列。在某些Windows系统上可以直接使用,有些需要额外配置,而在其他系统上使用则会导致输出显示混乱。

问题在于当Golang通过exec.Command启动外部进程时,默认情况下不会为子进程分配控制台终端(TTY)。当C#程序尝试执行Console.Clear()时,由于没有有效的控制台句柄,导致System.IO.IOException: 句柄无效异常。

以下是修复方案:

func ExecuteProcess(path string, parameters []string) error {
    cmd := exec.Command(path, parameters...)
    
    // 关键设置:将标准输入、输出和错误连接到当前进程
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    
    fmt.Printf("[ExecuteProcess] Running command and waiting for it to finish...\n")
    
    // 使用Run()而不是CombinedOutput(),因为我们已经直接连接了标准流
    err := cmd.Run()
    if err != nil {
        fmt.Printf("Command finished with error: %v\n", err)
        return err
    }
    
    fmt.Println("Command completed successfully")
    return nil
}

或者,如果你需要捕获输出但同时允许清屏操作,可以使用伪终端(pty):

import (
    "os"
    "os/exec"
    "github.com/creack/pty"
)

func ExecuteProcessWithPty(path string, parameters []string) error {
    cmd := exec.Command(path, parameters...)
    
    fmt.Printf("[ExecuteProcess] Running command with PTY...\n")
    
    // 创建伪终端
    ptyFile, err := pty.Start(cmd)
    if err != nil {
        return err
    }
    
    // 将PTY输出复制到标准输出
    go func() {
        io.Copy(os.Stdout, ptyFile)
    }()
    
    // 等待命令完成
    err = cmd.Wait()
    if err != nil {
        fmt.Printf("Command finished with error: %v\n", err)
        return err
    }
    
    fmt.Println("Command completed successfully")
    return nil
}

对于等待程序自行关闭的问题,上面的代码已经正确处理。cmd.Run()会等待进程执行完成,cmd.Wait()在使用PTY时也会等待进程结束。

如果你需要更细粒度的控制,可以使用以下方式:

func ExecuteProcessWithTimeout(path string, parameters []string, timeout time.Duration) error {
    cmd := exec.Command(path, parameters...)
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    
    fmt.Printf("[ExecuteProcess] Starting command...\n")
    
    if err := cmd.Start(); err != nil {
        return err
    }
    
    // 创建超时通道
    done := make(chan error, 1)
    go func() {
        done <- cmd.Wait()
    }()
    
    // 等待进程完成或超时
    select {
    case err := <-done:
        if err != nil {
            fmt.Printf("Command finished with error: %v\n", err)
            return err
        }
        fmt.Println("Command completed successfully")
        return nil
        
    case <-time.After(timeout):
        fmt.Printf("Command timed out after %v\n", timeout)
        cmd.Process.Kill()
        return fmt.Errorf("command timed out")
    }
}

使用示例:

// 基本用法
err := ExecuteProcess("test.exe", []string{"20"})

// 带PTY的用法(需要安装 github.com/creack/pty)
// err := ExecuteProcessWithPty("test.exe", []string{"20"})

// 带超时的用法
// err := ExecuteProcessWithTimeout("test.exe", []string{"20"}, 30*time.Second)

第一个解决方案是最简单的,通过将标准流直接连接到父进程,为C#程序提供了有效的控制台句柄,从而解决了清屏时的句柄无效错误。

回到顶部