如何“推迟” STDIN的阅读

时间:2018-09-24 13:36:07

标签: go stdin

在这个最小的工作示例中,我尝试执行以下操作:

  1. 提示用户输入密码
  2. 从指定为参数的文件或STDIN中解组JSON

这是源代码:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
    "syscall"

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

const correctPassword = "secret"

func main() {
    args := os.Args[1:]

    var passwd string

    for {
        passwd = promptPassword()
        if passwd == correctPassword {
            log.Println("Correct password! Begin processing...")
            break
        }
        log.Println("Incorrect password!")
    }

    if len(args) == 0 { // Read from stdin
        log.Println("Reading from stdin")
        dec := json.NewDecoder(os.Stdin)
        for {
            var v interface{}
            if err := dec.Decode(&v); err == io.EOF {
                break
            } else if err != nil {
                log.Fatal(err)
            }
            log.Printf("%#v", v)
        }
    }

    for _, fileName := range args {
        log.Println("Reading from", fileName)
        f, err := os.Open(fileName)
        if err != nil {
            log.Println(err)
            continue
        }
        defer f.Close()
        dec := json.NewDecoder(f)
        for {
            var v interface{}
            if err := dec.Decode(&v); err == io.EOF {
                break
            } else if err != nil {
                log.Fatal(err)
            }
            log.Printf("%#v", v)
        }
    }
}

func promptPassword() (passwd string) {
    for {
        fmt.Fprintln(os.Stderr, "Enter password:")
        b, _ := terminal.ReadPassword(int(syscall.Stdin))
        passwd = string(b)
        if passwd != "" {
            break
        }
    }
    return passwd
}

除了通过管道传输或重定向已准备好的数据(例如go run main.go < mydata.jsonecho 42 | go run main.go等)之外,一切正常。

当我通过管道将某些数据重定向到程序时,数据将由密码提示而不是JSON解码器部分处理。有什么方法可以在第一时间提示您输入密码,并且仅在处理输入数据之后?

我试图detect if there's any data in STDIN读取并存储在一些临时字节片中,但是我找不到如何关闭/截断STDIN的方法,因此它不会读取两次数据。

2 个答案:

答案 0 :(得分:1)

无需对程序进行任何更改,您可以在json之前的stdin中包含密码,例如(bash):{echo pass; cat data.json; } | goprogcat pass.txt data.json | goprog

有关更好的密码传递方法(例如环境或文件描述符),请查看sshpasshttps://linux.die.net/man/1/sshpass


您还可以缓冲所有stdin,并稍后(通过io.Reader)重用其内容


重新设计您的应用程序逻辑,使其功能可以接受io.Reader作为要解组的数据源。

main()中,如果命令行上没有文件参数,则将os.Stdin传递给所提到的函数,否则(尝试)打开文件并将其传递给解组函数。


注意:要决定是否打印提示,可以使用类似isatty的函数,该函数告诉stdin是否是交互式的:https://github.com/mattn/go-isatty

答案 1 :(得分:0)

您将无法执行此操作,因为外壳一旦完成写内容就将关闭stdin文件描述符。

一旦stdin关闭,您将无法重新打开它,这由外壳程序控制。

检查此程序,以测试此行为

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    io.Copy(os.Stdout, os.Stdin)
    fmt.Println("done")
    some := make([]byte, 100)
    _, err := os.Stdin.Read(some)
    fmt.Println(err)
    fmt.Println(string(some))
}

输出将为

$ echo "some" | go run main.go 
some
done
EOF