应使用 golang.org/x/term.readpassword 读取密码以隐藏输入,避免明文显示;执行命令须绕过 shell、用 os/exec.command 显式传参防注入;交互输入推荐 github.com/chzyer/readline;ctrl+c 需用 os/signal 捕获并安全终止子进程。

用 golang.org/x/term 读取密码或隐藏输入
默认的 fmt.Scanln 或 bufio.NewReader(os.Stdin).ReadString('\n') 会把密码明文打在终端上,这不行。得用 golang.org/x/term —— 它是 Go 官方维护的、跨平台的终端控制包,比自己调 syscall 稳定得多。
常见错误是直接 import syscall 或硬写 ANSI 转义序列,结果在 Windows 上崩,或在某些 IDE 内置终端里失效。
- 先
go get golang.org/x/term - 读密码:用
term.ReadPassword(int(os.Stdin.Fd())),它自动关回显、清缓存、处理 Ctrl+C - 注意:返回的是
[]byte,记得用string(pwd)转成字符串;别漏掉err检查,比如用户按 Ctrl+D 会返回io.EOF - Windows 下如果报
not a terminal,说明 stdin 被重定向了(比如管道或 IDE 运行),这时不能用ReadPassword,得降级提示并用普通读取(但要明确告诉用户“密码将明文显示”)
用 os/exec 安全执行用户命令,避免 shell 注入
交互式 Shell 工具常需要运行用户输入的命令,比如 ls -l /tmp。直接拼接字符串再丢给 sh -c 是高危操作——rm -rf /; echo "done" 这种输入会直接执行两条命令。
正确做法是绕过 shell,用 os/exec.Command 显式拆解命令和参数:
立即学习“go语言免费学习笔记(深入)”;
1、数据调用该功能使界面与程序分离实施变得更加容易,美工无需任何编程基础即可完成数据调用操作。2、交互设计该功能可以方便的为栏目提供个性化性息功能及交互功能,为产品栏目添加产品颜色尺寸等属性或简单的留言和订单功能无需另外开发模块。3、静态生成触发式静态生成。4、友好URL设置网页路径变得更加友好5、多语言设计1)UTF8国际编码; 2)理论上可以承担一个任意多语言的网站版本。6、缓存机制减轻服务器
- 不要:
exec.Command("sh", "-c", userCmd) - 要:
exec.Command("ls", "-l", "/tmp")—— 把用户输入按空格切分后,逐个传进参数列表(注意:简单空格分割不够健壮,真实场景建议用shlex.Split类逻辑,或限制只支持固定命令集) - 如果真要支持管道或重定向,别自己解析,改用
github.com/mitchellh/go-homedir+os/user做路径展开,然后交由 shell 执行,但必须对输入做白名单校验(比如只允许cat、grep、head等几个命令) -
cmd.Run()会阻塞,适合简单命令;需要实时输出时用cmd.StdoutPipe()+io.Copy,别用cmd.Output()吞大文件,容易 OOM
用 readline 库实现带历史和补全的输入行
纯 fmt.Print + bufio 只能实现“输完回车才响应”,没法上下键翻历史、Tab 补全、Ctrl+A 跳行首——这不是 Shell,是填空题。
Go 生态里最轻量靠谱的是 github.com/chzyer/readline(虽已归档,但稳定、无依赖、文档清晰)。别用 promptui 或 survey,它们面向表单,不是持续交互的 Shell。
- 初始化:
rl, _ := readline.New(readline.Config{Prompt: "> "}),记得defer rl.Close() - 历史记录自动保存在内存,想持久化就加
HistoryFile: "/path/to/history",它会自动读写 JSON - 补全靠
rl.SetCompleter(),函数接收当前输入前缀,返回匹配项列表;注意别在补全函数里做耗时操作(如查网络),会卡住整个行编辑 - Windows 下如果光标乱跳,确认没开 VS Code 的“integrated terminal”兼容模式,或换用
github.com/abiosoft/ishell(更重但 Win 支持更好)
信号处理:让 Ctrl+C 不直接 kill 进程
交互式工具里,用户按 Ctrl+C 通常想取消当前命令,而不是退出整个程序。默认行为是进程收到 SIGINT 后直接终止,没法拦截。
得用 os/signal 显式捕获,并区分场景处理:
- 全局注册:
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM),但别在主 goroutine 里阻塞,否则无法响应新命令 - 推荐方式:每个命令执行前起一个 goroutine 监听
SIGINT,一旦收到,调用cmd.Process.Kill()终止子进程,然后继续主循环 - 注意:
cmd.Run()是同步的,cmd.Start()+cmd.Wait()才能配合信号中断;同时记得恢复终端状态(比如readline被中断后可能残留原始模式) - macOS/Linux 下
SIGINT默认发给整个进程组,Windows 用os.Interrupt即可,不用syscall









