【问题标题】:How to read from device when stdin is pipe标准输入为管道时如何从设备读取
【发布时间】:2018-04-07 06:08:48
【问题描述】:

所以我有一个从 STDIN 读取的 Go 程序,如下所示。我希望从键盘或设备输入用户名和密码,但可以使用管道传递字符串切片。如果我运行如下命令:

echo "Hello World" | go run main.go

os.Stdin 将被设置为从管道读取,而不是从键盘读取。有没有办法我可以更改os.Stdin FileMode 使其从设备读取,即用户名和密码的键盘?

我尝试使用os.Stdin.Chmod(FileMode),但收到此错误:

chmod /dev/stdin: 无效参数

func main() {
  var n = []string{}
  scanner := bufio.NewScanner(os.Stdin)
  fmt.Println("Please type anything with Newline Separated, empty line signals termination")
  for scanner.Scan() {
    h := scanner.Text()
    if h == "" {
      break
    }
    n = append(n, h)
  }
  if err := scanner.Err(); err != nil {
    fmt.Printf("Error in reading from STDIN: %v\n", err)
  }

  reader := bufio.NewReader(os.Stdin)
    os.Stdout.WriteString("Username: ")
    username, err := reader.ReadString('\n')
    if err != nil {
        fmt.Printf("Unable to read username: %v\n", err)
  }
  username = strings.TrimSpace(username)

  os.Stdout.WriteString("Password: ")
  bytePassword, _ := terminal.ReadPassword(int(os.Stdin.Fd()))

  password := string(bytePassword)
  os.Stdout.WriteString("\n")
}

【问题讨论】:

  • 澄清一下,目标是从标准输入读取,然后从终端读取,即使终端没有连接到标准输入?
  • 目标是从程序开头设置为管道模式的stdin读取,从reader := bufio.NewReader(os.Stdin)这一行开始,stdin的模式切换到设备,即键盘

标签: go pipe stdin


【解决方案1】:

scanf 可能会有所帮助,请查看以下示例:

https://play.golang.org/p/tteQNl0trJp

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Enter your name")

    var name string
    fmt.Scanf("%s", &name)
    fmt.Printf("name = %s\n", name)
}

一些更详细的东西来检查是否有东西可以从标准输入读取,如果没有提示用户:

https://play.golang.org/p/7qeAQ5UNhdQ

package main

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

func main() {
    // check if there is somethinig to read on STDIN
    stat, _ := os.Stdin.Stat()
    if (stat.Mode() & os.ModeCharDevice) == 0 {
        var stdin []byte
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            stdin = append(stdin, scanner.Bytes()...)
        }
        if err := scanner.Err(); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("stdin = %s\n", stdin)
    } else {
        fmt.Println("Enter your name")

        var name string
        fmt.Scanf("%s", &name)
        fmt.Printf("name = %s\n", name)
    }
}

【讨论】:

    【解决方案2】:

    您可以改为从/dev/tty 读取,因为这始终是终端(如果程序在终端上运行)。这只能移植到类 Unix 系统(Linux、BSD、macOS 等),不能在 Windows 上运行。

    // +build !windows
    
    tty, err := os.Open("/dev/tty")
    if err != nil {
        log.Fatalf("can't open /dev/tty: %s", err)
    }
    scanner := bufio.NewScanner(tty)
    // as you were ...
    

    【讨论】:

    • 所以在您提供的答案中,程序将只从键盘读取,但我的意图是首先从管道读取,然后将其切换到设备模式。目标是从程序开头设置为管道模式的stdin读取,并从reader := bufio.NewReader(os.Stdin)这一行开始,stdin的模式切换到设备,即键盘。这甚至可能吗?谢谢。
    • @jap 仅当您想从 tty 读取时才从 tty 读取,而当您想从 stdin 读取时从 stdin 读取。当然,您一次可以拥有多个 bufio.Reader;只需使用另一个变量!