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

Go程序中stdin为管道时如何从键盘读取用户名密码

解决Go程序中管道重定向后读取键盘输入的问题

首先得明确:修改os.Stdin的FileMode完全没用——文件模式(权限位)只是控制文件的读写执行权限,和os.Stdin指向的输入源根本不是一回事。当你用echo "Hello World" | go run main.go时,os.Stdin已经被系统重定向到管道的读端,此时改权限也没法让它重新指向键盘。

正确的做法是直接打开控制终端设备来读取键盘输入,绕过被重定向的os.Stdin。

具体实现方案

1. Unix-like系统(Linux/macOS)

在这类系统中,控制终端的固定路径是/dev/tty,直接打开这个设备文件就能读取键盘输入:

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	// 先读取管道传入的内容(原os.Stdin)
	fmt.Println("从管道读取的内容:")
	pipeScanner := bufio.NewScanner(os.Stdin)
	for pipeScanner.Scan() {
		fmt.Println(pipeScanner.Text())
	}

	// 打开控制终端读取用户名密码
	tty, err := os.Open("/dev/tty")
	if err != nil {
		fmt.Fprintf(os.Stderr, "无法打开终端:%v\n", err)
		os.Exit(1)
	}
	defer tty.Close()

	ttyScanner := bufio.NewScanner(tty)
	fmt.Print("请输入用户名:")
	ttyScanner.Scan()
	username := ttyScanner.Text()

	fmt.Print("请输入密码:")
	ttyScanner.Scan()
	password := ttyScanner.Text()

	fmt.Printf("\n你输入的用户名:%s,密码:%s\n", username, password)
}

2. 跨平台兼容(支持Windows)

Windows系统的控制终端设备是CONIN$,可以通过平台判断来适配:

package main

import (
	"bufio"
	"fmt"
	"os"
	"runtime"
)

func getTerminal() (*os.File, error) {
	var ttyPath string
	switch runtime.GOOS {
	case "windows":
		ttyPath = "CONIN$"
	default:
		ttyPath = "/dev/tty"
	}
	return os.Open(ttyPath)
}

func main() {
	// 读取管道内容
	fmt.Println("从管道读取的内容:")
	pipeScanner := bufio.NewScanner(os.Stdin)
	for pipeScanner.Scan() {
		fmt.Println(pipeScanner.Text())
	}

	// 获取终端设备
	tty, err := getTerminal()
	if err != nil {
		fmt.Fprintf(os.Stderr, "无法打开终端:%v\n", err)
		os.Exit(1)
	}
	defer tty.Close()

	// 读取用户名密码
	ttyScanner := bufio.NewScanner(tty)
	fmt.Print("请输入用户名:")
	ttyScanner.Scan()
	username := ttyScanner.Text()

	fmt.Print("请输入密码:")
	ttyScanner.Scan()
	password := ttyScanner.Text()

	fmt.Printf("\n你输入的用户名:%s,密码:%s\n", username, password)
}

原理说明

/dev/tty(或Windows的CONIN$)是当前进程的控制终端设备文件,它直接关联用户的键盘输入,完全不受os.Stdin重定向的影响。不管你用管道、文件重定向还是其他方式修改了os.Stdin,打开这个设备都能直接和键盘交互。

优化小提示

如果需要隐藏密码输入(不显示明文),可以用golang.org/x/term包的ReadPassword函数,它会临时关闭终端回显:

import "golang.org/x/term"

// ...

fmt.Print("请输入密码:")
passwordBytes, err := term.ReadPassword(int(tty.Fd()))
if err != nil {
	fmt.Fprintf(os.Stderr, "读取密码失败:%v\n", err)
	os.Exit(1)
}
password := string(passwordBytes)
fmt.Println() // 手动换行,因为ReadPassword不会触发回车

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:40:33