Как «отложить» чтение от STDIN - PullRequest
0 голосов
/ 24 сентября 2018

В этом минимальном рабочем примере я пытаюсь сделать следующее:

  1. Запрашивать у пользователя пароль
  2. Unmarshal JSON либо из файлов, указанных в качестве аргументов, либо из STDIN

Вот исходный код:

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.json, или echo 42 | go run main.go и т. Д.).

Когда я передаю или перенаправляю некоторые данные в программу, данные обрабатываются паролем, а не частью JSON-декодера.Есть ли способ сначала запросить пароль и только после обработки входящих данных?

Я пытался обнаружить, есть ли какие-либо данные в STDIN , чтобы прочитать их и сохранить в некоторыхвременный байт, но я не могу найти, как закрыть / обрезать STDIN, поэтому он не будет читать данные дважды.

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Без каких-либо изменений в вашей программе вы можете включить пароль в stdin перед json, например (bash): {echo pass; cat data.json; } | goprog или cat pass.txt data.json | goprog

Для лучшего способа передачи пароля (например, среда или дескриптор файла)) посмотрите на sshpass: https://linux.die.net/man/1/sshpass


Вы также можете буферизовать весь стандартный ввод и использовать его содержимое позже (через io.Reader)


Переработать логику приложенияк функции, которая принимает io.Reader в качестве источника данных для unmarshall.

В main() передает os.Stdin в указанную функцию, если в командной строке нет аргумента файла, в противном случае (попробуйте) открыть файл и передатьэто функция отмены сортировки.


Примечание: для принятия решения о том, печатать приглашение или нет, вы можете использовать isatty подобную функцию, которая сообщает, является ли stdin интерактивным: https://github.com/mattn/go-isatty

0 голосов
/ 24 сентября 2018

Вы не сможете, потому что оболочка закроет дескриптор файла 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
...