Golang中如何在Windows终端获取提示字符串

Golang中如何在Windows终端获取提示字符串 我正在使用Go语言模拟一个Windows终端。 我希望它能与真实的Windows终端窗口完全一致,就像我在执行一个.bat文件一样。但我目前遇到的问题是,如何从cmd进程中获取并打印提示符字符串。 以下是我当前的代码:

package minion_core

import (
	"bufio"
	"fmt"
	"os/exec"
	"strings"
	"syscall"
	"time"
)

var TestMode = true

func Test() {
	// Start a new command提示符 process
	cmd := exec.Command("cmd.exe")
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

	// Create pipes for standard input and output
	stdin, err := cmd.StdinPipe()
	if err != nil {
		fmt.Println("Error creating stdin pipe:", err)
		return
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("Error creating stdout pipe:", err)

		return
	}

	// Start the command
	if err := cmd.Start(); err != nil {
		fmt.Println("Error starting command:", err)
		return
	}

	// Create a scanner to read the output
	scanner := bufio.NewScanner(stdout)

	// Variable to hold command output
	var output strings.Builder

	// Start a goroutine to read the command output
	go func() {
		for scanner.Scan() {
			fmt.Println("⚡ " + scanner.Text() + "\n")
		}
	}()

	// Function to execute commands
	executeCommand := func(command string) {
		_, err := stdin.Write([]byte(command + "\n"))
		if err != nil {
			fmt.Println("Error writing to stdin:", err)
		}
		// Wait a moment to ensure the output is captured
		time.Sleep(1 * time.Second)
		// Print the current output after executing the command
		fmt.Println("Command output:\n", output.String())
		// Clear the output for the next command
		output.Reset()
	}

	// Execute several commands
	executeCommand("echo Hello, World!")
	executeCommand("dir")
	executeCommand("nslookup")
	executeCommand("google.com")
	executeCommand("exit")
	executeCommand("cd ..")
	executeCommand("cd ..")
	executeCommand("exit") // Exit the command prompt (optional)

	// Close the stdin pipe
	stdin.Close()

	// Wait for the command to finish
	if err := cmd.Wait(); err != nil {
		fmt.Println("Error waiting for command:", err)
		return
	}
}

如你所见,我以一秒的间隔逐个执行命令。但我不知道如何打印cmd的实际提示符,而不是那个闪电符号。 如果你知道如何处理这个问题,请帮助我。


更多关于Golang中如何在Windows终端获取提示字符串的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何在Windows终端获取提示字符串的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Windows终端中获取提示字符串需要处理cmd.exe的特殊输出行为。以下是修改后的代码,可以正确捕获和显示提示符:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os/exec"
    "strings"
    "syscall"
    "time"
)

func main() {
    cmd := exec.Command("cmd.exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

    stdin, err := cmd.StdinPipe()
    if err != nil {
        fmt.Println("Error creating stdin pipe:", err)
        return
    }
    
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println("Error creating stdout pipe:", err)
        return
    }

    if err := cmd.Start(); err != nil {
        fmt.Println("Error starting command:", err)
        return
    }

    // 使用带缓冲的读取器
    reader := bufio.NewReader(stdout)
    
    // 读取初始提示符
    go func() {
        for {
            line, err := reader.ReadString('\n')
            if err != nil {
                if err == io.EOF {
                    break
                }
                fmt.Println("Read error:", err)
                break
            }
            
            // 清理输出并显示
            line = strings.TrimSpace(line)
            if line != "" {
                fmt.Println(line)
            }
        }
    }()

    // 给cmd.exe时间初始化
    time.Sleep(100 * time.Millisecond)
    
    // 发送命令并等待提示符重新出现
    commands := []string{
        "echo Hello, World!",
        "dir",
        "cd ..",
        "exit",
    }
    
    for _, command := range commands {
        // 发送命令
        fmt.Printf("> %s\n", command)
        stdin.Write([]byte(command + "\r\n"))
        
        // 等待命令执行完成(提示符重新出现)
        time.Sleep(500 * time.Millisecond)
    }
    
    // 关闭输入流
    stdin.Close()
    
    // 等待进程结束
    cmd.Wait()
}

更高级的实现,使用pty来更好地模拟终端:

package main

import (
    "fmt"
    "io"
    "os"
    "os/exec"
    "syscall"
    "unsafe"
    "golang.org/x/term"
)

// Windows控制台API常量
const (
    ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
    STD_OUTPUT_HANDLE                  = -11
)

var (
    kernel32           = syscall.NewLazyDLL("kernel32.dll")
    procGetStdHandle   = kernel32.NewProc("GetStdHandle")
    procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
    procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
)

func enableVTMode() error {
    h, _, _ := procGetStdHandle.Call(uintptr(STD_OUTPUT_HANDLE))
    
    var mode uint32
    procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode)))
    
    mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
    ret, _, err := procSetConsoleMode.Call(h, uintptr(mode))
    if ret == 0 {
        return err
    }
    return nil
}

func main() {
    // 启用VT模式以获得更好的终端支持
    enableVTMode()
    
    cmd := exec.Command("cmd.exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: false,
        CreationFlags: syscall.CREATE_NEW_CONSOLE,
    }
    
    stdin, _ := cmd.StdinPipe()
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    
    cmd.Start()
    
    // 合并stdout和stderr的读取
    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stderr, stderr)
    
    // 从标准输入读取并转发到cmd
    go func() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            stdin.Write([]byte(scanner.Text() + "\r\n"))
        }
    }()
    
    cmd.Wait()
}

使用conpty的替代方案(Windows 10+):

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "os/exec"
    "time"
    
    "github.com/ActiveState/termtest/conpty"
)

func main() {
    cpty, err := conpty.New(80, 25)
    if err != nil {
        panic(err)
    }
    defer cpty.Close()
    
    // 启动cmd.exe
    cmd := exec.Command("cmd.exe")
    pid, err := cpty.Spawn(cmd.Path, cmd.Args, &cmd.SysProcAttr)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Spawned process with PID: %d\n", pid)
    
    // 读取输出
    go func() {
        buf := make([]byte, 1024)
        for {
            n, err := cpty.Read(buf)
            if err != nil {
                break
            }
            fmt.Print(string(buf[:n]))
        }
    }()
    
    // 发送命令
    commands := []string{
        "echo Prompt test",
        "cd /",
        "dir",
        "exit",
    }
    
    for _, cmd := range commands {
        cpty.Write([]byte(cmd + "\r\n"))
        time.Sleep(500 * time.Millisecond)
    }
    
    // 等待进程结束
    process, _ := os.FindProcess(int(pid))
    process.Wait()
}

最简单的解决方案,直接使用cmd的/K参数保持提示符:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os/exec"
    "strings"
    "syscall"
)

func main() {
    cmd := exec.Command("cmd.exe", "/K", "prompt $P$G")
    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
    
    stdin, _ := cmd.StdinPipe()
    stdout, _ := cmd.StdoutPipe()
    
    cmd.Start()
    
    // 读取输出
    go func() {
        reader := bufio.NewReader(stdout)
        for {
            line, err := reader.ReadString('\r')
            if err != nil {
                break
            }
            // 清理并显示输出
            line = strings.TrimRight(line, "\r\n")
            if strings.Contains(line, "Microsoft") || 
               strings.Contains(line, "Copyright") ||
               strings.Contains(line, "Administrator:") {
                continue
            }
            fmt.Println(line)
        }
    }()
    
    // 发送命令
    commands := []string{
        "cd /",
        "echo Testing prompt",
        "dir",
        "exit",
    }
    
    for _, c := range commands {
        stdin.Write([]byte(c + "\r\n"))
    }
    
    stdin.Close()
    cmd.Wait()
}

这些示例展示了不同的方法来获取和显示Windows终端的提示字符串。第一种方法是最直接的,通过适当的延迟和输出处理来确保提示符能够正确显示。

回到顶部