当 go 程序通过管道(如 `tail -f file | go run main.go`)接收标准输入时,`os.stdin` 已被重定向,无法再响应键盘输入;但可通过打开 `/dev/tty` 直接访问控制终端,实现“后台读管道 + 前台读键盘”的双通道输入。
在 POSIX 系统(包括 Linux 和 macOS)中,每个进程都关联一个控制终端(controlling terminal),即使 stdin 被重定向(如来自管道或文件),/dev/tty 仍始终指向该终端设备。这使得程序能在处理流式输入的同时,独立、实时地捕获用户按键——无需 fork 子进程、不依赖外部命令,也规避了进程残留风险。
以下是一个完整、健壮的示例程序,使用 goroutine 并发读取两个输入源:
package main
import (
"fmt"
"io"
"log"
"os"
)
func readFromStdin() {
buf := make([]byte, 1024)
for {
n, err := os.Stdin.Read(buf)
if err != nil && err != io.EOF {
log.Printf("error reading stdin: %v", err)
return
}
if n > 0 {
fmt.Printf("→ FROM PIPE: %s", string(buf[:n]))
}
if err == io.EOF {
fmt.Println("→ PIPE CLOSED")
return
}
}
}
func readFromTTY() {
tty, err := os.Open("/dev/tty")
if err != nil {
log.Fatal("failed to open /dev/tty: ", err)
}
defer tty.Close()
buf := make([]byte, 1024)
for {
n, err := tty.Read(buf)
if err != nil && err != io.EOF {
log.Printf("error reading TTY: %v", err)
return
}
if n > 0 {
fmt.Printf("← FROM KEYBOARD: %s", string(buf[:n]))
}
if err == io.EOF {
return
}
}
}
func main() {
go readFromStdin()
readFromTTY() // 主 goroutine 阻塞在此,避免主程序退出
}
✅ 关键说明: