一个在Golang中接受用户的普通和秘密输入的终端CLI应用程序

445 阅读1分钟

在这个例子中,我们将用Golang创建一个终端(CLI)应用程序。该命令将有两个必要的标志。在这里阅读标志包。它还将逐步向用户提示三个问题。其中一个问题是一个秘密值,所以我们将确保终端不会回音。对于秘密输入,我们使用终端包。

代码

package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

const usage = `Description: Login to server

Usage: %s [options]

Options:
`

var input struct {
	env string
	uid string
	psw string
	key string
	pin string
}

func main() {
	// Collect user input
	flag.StringVar(&input.env, "env", input.env, "The environment to login - {dev|stag|prod}")
	flag.StringVar(&input.uid, "uid", input.uid, "The user identifier.")
	flag.Usage = func() {
		_, _ = fmt.Fprintf(flag.CommandLine.Output(), usage, os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	// Validate user input
	validate()

	// Prompt user for more input
	key, err := promptPlain("What is your key?")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	input.key = key

	psw, err := promptSecret("What is your password?")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	input.psw = psw

	pin, err := promptPlain("What is your pin?")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	input.pin = pin

	// Print user input
	fmt.Printf("%+v\n", input)
}

// validate validates user input.
func validate() {
	switch input.env {
	case "dev", "stag", "prod":
		break
	default:
		fmt.Println("The -env option is required.")
		os.Exit(2)
	}

	if len(input.uid) == 0 {
		fmt.Println("The -uid option is required.")
		os.Exit(2)
	}
}

// promptPlain prompts user for an input that is echo-ed on terminal.
func promptPlain(question string) (string, error) {
	fmt.Printf(question + "\n> ")

	reader := bufio.NewReader(os.Stdin)
	for {
		answer, err := reader.ReadString('\n')
		if err != nil {
			return "", err
		}

		answer = strings.TrimSuffix(answer, "\n")
		return answer, nil
	}
}

// promptSecret prompts user for an input that is not echo-ed on terminal.
func promptSecret(question string) (string, error) {
	fmt.Printf(question + "\n> ")

	raw, err := terminal.MakeRaw(0)
	if err != nil {
		return "", err
	}
	defer terminal.Restore(0, raw)

	var (
		prompt string
		answer string
	)

	term := terminal.NewTerminal(os.Stdin, prompt)
	for {
		char, err := term.ReadPassword(prompt)
		if err != nil {
			return "", err
		}
		answer += char

		if char == "" || char == answer {
			return answer, nil
		}
	}
}
$ go build -race -o login main.go
$ ./login --help
Description: Login to server

Usage: ./login [options]

Options:
  -env string
    	The environment to login - {dev|stag|prod}
  -uid string
    	The user identifier.

测试

错误

$ ./login
The -env option is required.

$ ./login -env dev
The -uid option is required.

成功

正如你在下面看到的,我回答了 "你的密码是什么?"但终端没有回声

$ ./login -env dev -uid inanzzz
What is your key?
> my-key
What is your password?
> 
What is your pin?
> my-pin

{env:dev uid:inanzzz psw:my-password key:my-key pin:my-pin}