You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go程序输出时如何实现控制台输入控制程序终止?

解决Go应用中输入输出冲突的问题

你的问题本质是终端规范模式导致输入与输出共用同一光标位置,大量输出会直接覆盖正在输入的内容。下面提供两种可行的解决方法:

方法一:标准库实现终端原始模式

通过修改终端属性,让输入在后台读取,手动控制输入回显和光标位置,避免输出干扰输入行:

package main

import (
	"bufio"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [32]uint8
	Ispeed uint32
	Ospeed uint32
}

var (
	process       bool = true
	originalTerm  Termios
)

func saveTerminalState(fd uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(&originalTerm)))
	if errno != 0 {
		return errno
	}
	return nil
}

func restoreTerminalState(fd uintptr) {
	syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCSETS, uintptr(unsafe.Pointer(&originalTerm)))
}

func commandHandler() {
	defer restoreTerminalState(os.Stdin.Fd())

	// 设置终端为原始模式,关闭规范模式与自动回显
	var rawTerm Termios
	rawTerm = originalTerm
	rawTerm.Lflag &^= syscall.ICANON | syscall.ECHO
	rawTerm.Cc[syscall.VMIN] = 1
	rawTerm.Cc[syscall.VTIME] = 0

	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, os.Stdin.Fd(), syscall.TCSETS, uintptr(unsafe.Pointer(&rawTerm)))
	if errno != 0 {
		fmt.Println("Failed to set terminal to raw mode:", errno)
		return
	}

	reader := bufio.NewReader(os.Stdin)
	inputBuf := make([]byte, 0, 100)
	for process {
		b, err := reader.ReadByte()
		if err != nil {
			break
		}

		switch b {
		case '\n', '\r':
			cmd := string(inputBuf)
			if cmd == "stop" {
				process = false
				fmt.Println("\nStopping...")
			}
			inputBuf = inputBuf[:0]
			fmt.Print("\n> ")
		case '\b':
			if len(inputBuf) > 0 {
				inputBuf = inputBuf[:len(inputBuf)-1]
				fmt.Print("\b \b")
			}
		default:
			inputBuf = append(inputBuf, b)
			fmt.Print(string(b))
		}
	}
}

func task() {
	var i int64 = 0
	for process && i < 10000000000 {
		if i%30000000 == 0 {
			fmt.Printf("\r%d\n> ", i)
		}
		i++
	}
}

func main() {
	err := saveTerminalState(os.Stdin.Fd())
	if err != nil {
		fmt.Println("Failed to save terminal state:", err)
		return
	}

	fmt.Println("Enter 'stop' to terminate:")
	fmt.Print("> ")
	go task()
	commandHandler()
}

方法二:使用第三方终端库(更简洁)

借助成熟的第三方库github.com/pkg/term封装终端控制细节,减少手动处理的代码:

package main

import (
	"fmt"
	"github.com/pkg/term"
	"os"
)

var process bool = true

func commandHandler() {
	t, err := term.Open("/dev/tty")
	if err != nil {
		fmt.Println("Failed to open terminal:", err)
		return
	}
	defer t.Close()
	defer term.Restore(os.Stdin.Fd())

	term.RawMode(t)
	fmt.Println("Enter 'stop' to terminate:")
	fmt.Print("> ")

	inputBuf := make([]byte, 0, 100)
	for process {
		b := make([]byte, 1)
		n, err := t.Read(b)
		if err != nil || n == 0 {
			break
		}

		switch b[0] {
		case '\n', '\r':
			cmd := string(inputBuf)
			if cmd == "stop" {
				process = false
				fmt.Println("\nStopping...")
			}
			inputBuf = inputBuf[:0]
			fmt.Print("\n> ")
		case '\b':
			if len(inputBuf) > 0 {
				inputBuf = inputBuf[:len(inputBuf)-1]
				fmt.Print("\b \b")
			}
		default:
			inputBuf = append(inputBuf, b[0])
			fmt.Print(string(b))
		}
	}
}

func task() {
	var i int64 = 0
	for process && i < 10000000000 {
		if i%30000000 == 0 {
			fmt.Printf("\r%d\n> ", i)
		}
		i++
	}
}

func main() {
	go task()
	commandHandler()
}

核心逻辑说明

  • 通过切换终端到原始模式,分离输入与输出的光标控制,避免输出覆盖输入行
  • 手动处理输入回显、退格等操作,保证正常的输入交互体验
  • 输出时用\r和\n组合,将光标定位到新行,不干扰输入提示符

内容的提问来源于stack exchange,提问作者Nazarevsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 04:45:34