Search code examples
gopipestdin

How to read from device when stdin is pipe


So I have a Go program that reads from STDIN as such below. I want the username and password to be entered from keyboard or device but the string slice can be passed using pipe. If I run the command as below:

echo "Hello World" | go run main.go

os.Stdin will be set to read from pipes and never the keyboard. Is there a way that I can change os.Stdin FileMode as such that it will be reading from device, i.e. keyboard for username and password?

I tried using os.Stdin.Chmod(FileMode) but received this error:

chmod /dev/stdin: invalid argument

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")
}

Solution

  • You can instead read from /dev/tty as this is always the terminal (if the program runs on a terminal). This is portable only to Unix-like systems (Linux, BSD, macOS, etc) and won't work on 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 ...