当 go 程序通过管道(如 `tail -f file | go run main.go`)接收标准输入时,`os.stdin` 已被重定向,无法再读取用户键盘输入;但可通过打开 `/dev/tty` 直接访问控制终端,实现双路输入并行处理。
在 Unix/Linux 系统中,每个进程都关联一个控制终端(controlling terminal),即使 stdin 被重定向(例如来自管道或文件),/dev/tty 仍始终指向该终端设备。这使得程序能在接收流式数据的同时,响应用户实时键盘输入——无需 fork 子进程、不依赖外部 tail、也规避了进程生命周期管理风险。
以下是一个健壮的双输入示例:
package main
import (
"fmt"
"io"
"log"
"os"
"sync"
)
func readFromStdin(wg *sync.WaitGroup) {
defer wg.Done()
buf := make([]byte, 1024)
for {
n, err := os.Stdin.Read(buf)
if n > 0 {
fmt.Printf("← STDIN: %s", string(buf[:n]))
}
if err != nil {
if err != io.EOF {
log.Printf("STDIN read error: %v", err)
}
return
}
}
}
func readFromTTY(wg *sync.WaitGroup) {
defer wg.Done()
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 n > 0 {
fmt.Printf("→ TTY: %s", string(buf[:n]))
}
if err != nil {
if err != io.EOF {
log.Printf("TTY read error: %v", err)
}
return
}
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go readFromStdin(&wg)
go readFromTTY(&wg)
wg.Wait() // 等待两路输入均结束(通常 TTY 需手动 Ctrl+D 或关闭)
}
✅ 关键要点说明: