Read from console while piping commands in Go - shell

I need a way to read input from console twice (1 -from cat outupt, 2 - user inputs password), like this:
cat ./test_data/test.txt | app account import
My current code skips password input:
reader := bufio.NewReader(os.Stdin)
raw, err := ioutil.ReadAll(reader)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
wlt, err := wallet.Deserialize(string(raw))
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
fmt.Print("Enter password: ")
pass := ""
fmt.Fscanln(reader, &pass)
Also tried to read password with Scanln - doesn't works.
Note:
cat (and piping at all) can't be used with user input, as shell redirects inputs totally.
So the most simple solutions are:
to pass filename as argument
redirect manually app account import < ./test.txt

Read file and password separately (and don't show password), try this:
package main
import (
"fmt"
"io/ioutil"
"log"
"github.com/howeyc/gopass"
)
func main() {
b, err := ioutil.ReadFile("./test_data/test.txt") // just pass the file name
if err != nil {
fmt.Print(err)
}
fmt.Println(string(b))
fmt.Print("Password: ")
pass, err := gopass.GetPasswd()
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(pass))
}
and go get github.com/howeyc/gopass first.

Related

How to read file/path containing tilde

The following code gives ENOENT (2) Do you know how to get stat of a file containing tilde?
file := "~/.zshrc"
fileStat, err := os.Stat(file)
if err != nil {
return 0, err
}
The tilde is not something that the file system calls are able to interpret - it has a meaning only in shells like bash, which typically interprets it as $HOME. So you'll probably want to use os.Getenv("HOME") and then replace ~ with the result. Or, as suggested by Allon Guralnek in a comment, use os.UserHomeDir(), which reads the appropriate environment variable depending on your OS.
You can access the home directory of the current user using the os/user package.
Something like this will get you near what you want:
package main
import (
"fmt"
"log"
"os"
"os/user"
)
func main() {
usr, err := user.Current()
if err != nil {
log.Fatal(err)
}
fmt.Println(usr.HomeDir)
file := usr.HomeDir + "/.zshrc"
fileStat, err := os.Stat(file)
if err != nil {
log.Fatal(err)
}
fmt.Println(fileStat)
}

How to automate two os.Stdin input using Bash

I need to automate the input of a code segment like bellow where the inputs of the ReadString are distinct.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
buf := bufio.NewReader(os.Stdin)
value, err := buf.ReadString('\n')
if err != nil {
fmt.Println(err)
} else {
fmt.Println(value)
}
buf = bufio.NewReader(os.Stdin)
value, err = buf.ReadString('\n')
if err != nil {
fmt.Println(err)
} else {
fmt.Println(value)
}
}
I have tried several formats like the bellow one following answers of this question, unfortunately, none worked.
>> echo "data1
data2" | go run main.go
output: data1
EOF
Here data1 and data2 and input of the separate ReadString methods. I don't have control over the source code. So, I can only try changing the bash input. How to resolve this issue?
This is happening because the second string does not end with a newline. Looking at the documentation for ReadString:
If ReadString encounters an error before finding a delimiter, it returns the data read before the error and the error itself (often io.EOF).
So, even though error is non-nil, you have the data. The following change should work for this specific case:
buf = bufio.NewReader(os.Stdin)
value, err = buf.ReadString('\n')
if value!="" {
fmt.Println(value)
}
if err != nil {
fmt.Println(err)
}
In general, even if you get an error from ReadString, you may still have nonempty data returned from the function.

How to execute interactive CLI command in golang?

I'm trying to execute a command that asks for several inputs for example if you try to copy a file from local device to the remote device we use scp test.txt user#domain:~/ then it asks us for the password. What I want is I want to write a go code where I provide the password in the code itself for example pass:='Secret Password'. Similarly, I have CLI command where it asks us for several things such as IP, name, etc so I need to write a code where I just declare all the values in the code itself and when I run the code it doesn't ask anything just take all the inputs from code and run CLI command in case of copying file to remote it should not ask me for password when I run my go binary it should directly copy my file to remote decide.
func main() {
cmd := exec.Command("scp", "text.txt", "user#domain:~/")
stdin, err := cmd.StdinPipe()
if err = cmd.Start(); err != nil {
log.Fatalf("failed to start command: %s", err)
}
io.WriteString(stdin, "password\n")
if err = cmd.Wait(); err != nil {
log.Fatalf("command failed: %s", err)
}
}
If I use this code it is stuck on user#domain's password:
And no file is copied to the remote device.
Solution 1
You can bypass this with printf command
cmd := "printf 'John Doe\nNew York\n35' | myInteractiveCmd"
out, err := exec.Command("bash", "-c", cmd).Output()
Solution 2
You can use io.Pipe(). Pipe creates a synchronous in-memory pipe and you can write your answers into io.Writer and your cmd will read from io.Reader.
r, w := io.Pipe()
cmd := exec.Command("myInteractiveCmd")
cmd.Stdin = r
go func() {
fmt.Fprintf(w, "John Doe\n")
fmt.Fprintf(w, "New York\n")
fmt.Fprintf(w, "35\n")
w.Close()
}()
cmd.Start()
cmd.Wait()
Testing info
To test this I wrote cmd which asks for name, city, age and writes the result in file.
reader := bufio.NewReader(os.Stdin)
fmt.Print("Name: ")
name, _ := reader.ReadString('\n')
name = strings.Trim(name, "\n")
...
One way to go about this is to use command-line flags:
package main
import (
"flag"
"fmt"
"math"
)
func main() {
var (
name = flag.String("name", "John", "Enter your name.")
ip = flag.Int("ip", 12345, "What is your ip?")
)
flag.Parse()
fmt.Println("name:", *name)
fmt.Println("ip:", *ip)
}
Now you can run the program with name and ip flags:
go run main.go -name="some random name" -ip=12345678910`
some random name
ip: 12345678910
This channel is a good resource—he used to work for the Go team and made tons of videos on developing command-line programs in the language. Good luck!
I come across this question when trying to run the linux make menuconfig through golang os/exec.
To accomplish what you are trying to achieve try to set the cmd.Stdin to os.Stdin. Here is a working example:
package main
import (
"fmt"
"os"
"os/exec"
)
type cmdWithEnv struct {
pwd string
command string
cmdArgs []string
envs []string
}
func runCommand(s cmdWithEnv) error {
cmd := exec.Command(s.command, s.cmdArgs...)
if len(s.pwd) != 0 {
cmd.Dir = s.pwd
}
env := os.Environ()
env = append(env, s.envs...)
cmd.Env = env
fmt.Printf("%v\n", cmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin // setting this allowed me to interact with ncurses interface from `make menuconfig`
err := cmd.Start()
if err != nil {
return err
}
if err := cmd.Wait(); err != nil {
return err
}
return nil
}
func buildPackage() {
makeKernelConfig := cmdWithEnv{
pwd: "linux",
command: "make",
cmdArgs: []string{"-j12", "menuconfig"},
envs: []string{"CROSS_COMPILE=ccache arm-linux-gnueabihf-", "ARCH=arm"},
}
runCommand(makeKernelConfig)
}
func main() {
buildPackage()
}

How to “postpone” reading from STDIN

In this minimal working example I'm trying to do the following:
Prompt user for password
Unmarshal JSON either from files specified as arguments or from STDIN
Here's the source code:
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
}
Everything works all right except when already prepared data is piped or redirected (e.g. go run main.go < mydata.json, or echo 42 | go run main.go, etc).
When I pipe or redirect some data to the program, the data gets processed by the password prompt, not the JSON decoder part. Is there any way to at first prompt for the password, and only after process the incoming data?
I was trying to detect if there's any data in STDIN to read it and store in some temporary bytes slice, but I can't find how to close/truncate the STDIN, so it won't read data twice.
Without any changes to your program, you can include password in stdin before json, eg (bash): {echo pass; cat data.json; } | goprog, or cat pass.txt data.json | goprog
For better method for password passing (eg environment or file descriptor) look at sshpass: https://linux.die.net/man/1/sshpass
You can also buffer all stdin, and reuse its content later (via io.Reader)
Redesign your application logic to function which accept io.Reader as source of data to unmarshall.
In main() pass os.Stdin to mentioned function if there is no file argument on command line, otherwise (try to) open file and pass it to unmarshalling function.
Note: for deciding whether to print prompt or not you may use isatty like function, which tells if stdin is interactive: https://github.com/mattn/go-isatty
You wont be able because the shell will close the stdin file descriptor once it has finished to write the content.
Once stdin is closed you cant re open it, this is controlled by the shell.
Check this program, to test this behavior
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))
}
The output will be
$ echo "some" | go run main.go
some
done
EOF

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")
}
Probably scanf could help, check this example:
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)
}
Something a little more elaborated to check if there is something to read from stdin and if not prompt the user:
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)
}
}
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 ...

Resources