Go - write to stdin on external command - go

I have the following code which executes an external command and output to the console two fields waiting for the user input.
One for the username and other for the password, and then I have added them manually.
Could anyone give me a hint about how to write to stdin in order to enter these inputs from inside the program?
The tricky part for me is that there are two different fields waiting for input, and I'm having trouble to figure out how to fill one after the other.
login := exec.Command(cmd, "login")
login.Stdout = os.Stdout
login.Stdin = os.Stdin
login.Stderr = os.Stderr
err := login.Run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
SOLUTION:
login := exec.Command(cmd, "login")
var b bytes.Buffer
b.Write([]byte(username + "\n" + pwd + "\n"))
login.Stdout = os.Stdout
login.Stdin = &b
login.Stderr = os.Stderr

I imagine you could use a bytes.Buffer for that.
Something like that:
login := exec.Command(cmd, "login")
buffer := bytes.Buffer{}
buffer.Write([]byte("username\npassword\n"))
login.Stdin = &buffer
login.Stdout = os.Stdout
login.Stderr = os.Stderr
err := login.Run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
The trick is that the stdin is merely a char buffer, and when reading the credentials, it will simply read chars until encountering a \n character (or maybe \n\r). So you can write them in a buffer in advance, and feed the buffer directly to the command.

Related

Command executes but code doesn't return error for non existing commands

Code in function to run a fzf against an input, while debugging i discovered my code doesn't return errors, this code runs successfully:
reader := strings.NewReader(listOutput.String())
r, w, _ := os.Pipe()
os.Stdout = w
cmd := exec.Command("fzf", "--multi")
cmd.Stdin = reader
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Couldn't call fzf: %v", err)
}
w.Close()
So i changed the command to something that doesn't exist, but the code still doesn't return "couldn't call command: command not found", just exits.
reader := strings.NewReader(listOutput.String())
r, w, _ := os.Pipe()
os.Stdout = w
cmd := exec.Command("idontexist")
cmd.Stdin = reader
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Couldn't call command: %v", err)
}
w.Close()
I don't have an idea what could be wrong.
cmd.Run() does return an error, and your if block gets properly executed, but since you change the standard output os.Stdout = w you just don't see the result on your console / terminal.
The fmt package writes to the standard output.
If you use the log package, you will see the error as the log package writes to the standard error (which you didn't change):
log.Printf("Couldn't call command: %v", err)
This will output something like (note the default log format includes the timestamp too):
2022/12/07 13:46:19 Couldn't call command: exec: "idontexist": executable file not found in $PATH
Or don't change the standard output.
Also do note that log.Println() and fmt.Println() do not require a format string. Do use log.Printf() and fmt.Printf() when you specify a format string and arguments.

Thread-safe operation with Stdout and Stderr (exec. Cmd)

I have code and it works in the correct way, but it isn’t thread-safety https://play.golang.org/p/8EY3i1Uk_aO in these rows race happens here.
stdout := cmd.Stdout.(*bytes.Buffer).String()
stderr := cmd.Stderr.(*bytes.Buffer).String()
I rewrote it in this way
readout, _ := cmd.StdoutPipe()
readerr, _ := cmd.StderrPipe()
The link
https://play.golang.org/p/htbn2zXXeQk
I don’t like that MultiReader is used here and I cannot separate data stdout from stderr
r, _ := bufio.NewReader(io.MultiReader(readout, readerr)).ReadString('\n')
Also the second example doesn’t work (it is commented in the code). I expected stdout not to be empty (like here https://play.golang.org/p/8EY3i1Uk_aO)
How to make the logic like in the first example, but it should be thread-safety?
You have to pump cmd.Stdout and cmd.Stderr in separate goroutines until they are closed, for example, like you did with cmd.Stdin (but in other direction, of course). Otherwise there's a risk of deadlock - the process is blocked waiting to write to stdout/stderr, and your program is blocked waiting for the process to finish.
Or, like #JimB said, just assign string buffers to cmd.Stdout and cmd.Stderr, they will be filled as the process runs.
func invoke(cmd *exec.Cmd) (stdout string, stderr string, err error) {
stdoutbuf, stderrbuf := new(strings.Builder), new(strings.Builder)
cmd.Stdout = stdoutbuf
cmd.Stderr = stderrbuf
err = cmd.Start()
if err != nil {
return
}
err = cmd.Wait()
return stdoutbuf.String(), stderrbuf.String(), err
}
Live demo:
https://play.golang.org/p/hakSVNbqirB
I used the advice of #rusty, anyway the race
line `runner.go:264 ' is
append(normalizeEncoding(stdoutbuf.String()), normalizeEncoding(stderrbuf.String()), false)
solution:
create your own Writer wrapper
type lockedWriter struct {
sync.RWMutex
buf []byte
w io.Writer
}
func (w *lockedWriter) Write(b []byte) (n int, err error) {
w.Lock()
defer w.Unlock()
w.buf = append(w.buf, b...)
return w.w.Write(b)
}
func (w *lockedWriter) String() string {
w.RLock()
defer w.RUnlock()
return string(w.buf)
}
usage
stdoutbuf, stderrbuf := &lockedWriter{w: new(strings.Builder)}, &lockedWriter{w: new(strings.Builder)}
cmd.Stdout = stdoutbuf
cmd.Stderr = stderrbuf

Writing to exec.Cmd.StdinPipe() doesn't do anything

I'm creating a new command with exec, obtaining the Stdin io.WriteCloser and writing to it as follows:
cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
cmdInWriter, err := cmd.StdinPipe()
err = cmd.Start()
go func() {
for {
var c string
_, err = fmt.Scanln(&c)
written, err := io.WriteString(cmdInWriter, c)
fmt.Println(written) // prints 4, if c is "help"
}
}()
However, this doesn't seem to actually write to the program.
flag.Arg(0) = java
flag.Args()[1:]... = an array of arguments passed to the java program
I tested it with another program I quickly made (the previous list does not apply there) and the string was again not being written. What am I doing wrong?
You are missing the linefeed. When you read a string using Scanln, the resulting string does not have the linefeed at the end:
written, err := fmt.Fprintln(cmdInWriter, c)

How to capture/log everything after spawning an interactive program

I have a method that can spawn an interactive process, now how do I log everything (including stdin and stdout) after spawning ?
e.g.,
func execute(cmd1 string, slice []string) {
cmd := exec.Command(cmd1, slice...)
// redirect the output to terminal
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Run()
}
..
The interactive program could be :
execute(ftp)
I think I have to dup stdin, stdout and read write in separate thread.
Rather than redirecting it's output to the terminal read it and then you can log/print do whatever you want with it.
stdout, err := cmd.StdoutPipe()
b, _ := ioutil.ReadAll(stdout)
fmt.Println(string(b))
Something like the code above would work though there are many options. I think you'll want to remove all that code you have to redirect to the terminal.
you could store the output in a temporary buffer and write it to several places
outBuf := bytes.Buffer{}
cmd := exec.Command(cmd1, slice...)
cmd.Stdout = &outBuf
cmd.Run()
if outBuf.Len() > 0 {
log.Printf("%s", outBuf.String())
fmt.Fprintf(os.Stdout, "%s", outBuf.String())
}

How to pass []byte to external exiftool via stdin?

I am trying to do in golang the bash equivalent:
cat image.jpg | exiftool -author=some_auth - > updated_image.jpg
The exiftool '-' option makes it read from stdin, but say I have the image stored in a variable like
var img []bytes //in golang
I want stdin to contain the bytes from img and the system call to exiftool to read these from stdin, save the result (stdout) in another []byte - I am new to golang, how what do i approach this ?
I don't want to save to temp files on disk.
Thanks
Something like
out, err := os.Create("updated_image.jpg")
if err != nil {
log.Fatal(err)
}
cmd := exec.Command("exiftool", "-author=some_auth", "-")
cmd.Stdout = out
cmd.Stdin = bytes.NewReader(img)
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
out.Close()
should work.
Note that I haven't tested the code.

Resources