Called program fails if run through os.exec - go

I am not sure if this is a problem of the called program or if the problem is caused by the way I call the program. Because of this I start at the source code.
I need to call ssh from a program (if you are interested in the reasons I will mention them below) but ssh silently exits.
When I call ssh -v user#remotehost from shell this succeeds:
the wanted debug output on stderr is shown
I am asked for the password
I can see the remote hosts shell
But when I do the same from within my program (myssh -v user#remotehost only this happens:
I am asked for the password
Neither the debug output on stderr is shown nor do I reach the remote hosts shell.
This is my sourcecode:
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
params := os.Args[1:]
fmt.Printf("passing this to ssh: %s\n", params)
cmd := exec.Command("ssh", params...)
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
}
Reason why I wrote this code: I use Ansible which calls ssh. I need to "manipulate" the parameters that Ansible passes to ssh. So far I asked on the OpenSSH and Ansible mailing lists, there is no means in OpenSSH and Ansible to change the parameters (for others it is, but not those I need). The best suggestion I got and that I want to implement is to provide an alternative ssh command to Ansible, use that to receive and modify the parameters and pass them on to the real ssh.

Are you capturing Stdout and Stderr from cmd? This is where the respective outputs of the command is sent to. The documentation for exec.Command has a really good example on this.
In your case, you'll also want to setup Stdin so that you can pass the password for example.
Here's a really basic example based on your code:
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
)
func main() {
params := os.Args[1:]
fmt.Println("Passing this to ssh: %s", params)
var stdin bytes.Buffer
var stdout bytes.Buffer
var stderr bytes.Bufer
cmd := exec.Command("ssh", params...)
cmd.Stdin = &stdin
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Println("Stdout: %s", stdout.String())
fmt.Println("stderr: %s", stderr.String())
}
Since stdin, stdout, and stderr are all bytes.Buffers, you can read and write from them just like any other buffer.
You might also want to consider using the golang.org/x/crypto/ssh package which provides a native SSH interface for Go instead of using sub-processes.

Related

Run program w/ exec.Command redirecting stdout to /dev/null

I am translating my script from bash to go and unfortunately I am not able to make pass command work. When I run the bash one I receive a window to provide my password to the pass manager.
Here is part of my go code:
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)
func run_command(command *exec.Cmd) string {
attach_json, err := command.Output()
if err != nil {
fmt.Println(err.Error())
// os.Exit(0)
}
fmt.Println(attach_json)
return string(attach_json)
}
email := "xyz#abc.com"
cmd_emamil := "GoJira/api-token:" + email
pass_cmd := exec.Command("pass", cmd_emamil, "> /dev/null")
pass_cmd := exec.Command("pass", cmd_emamil)
run_command(pass_cmd)
In the shell command pass >/dev/null, >/dev/null is not an argument to pass; instead, it's an instruction to the shell, telling it to replace file descriptor 1 (stdout) with a handle on /dev/null before pass is started.
When you use exec.Command() there is no shell, so you can't use shell syntax. Instead, assign the file you want stdout to be redirected to to the Stdout of your exec.Command.
devnull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0755)
if err != nil { panic(err) }
cmd_email := "GoJira/api-token:" + email
pass_cmd := exec.Command("pass", cmd_email)
pass_cmd.Stdout = devnull

Spawning a docker command yields an empty result

I have the following Go code, where I try to spawn docker command and get its output:
package main
import "fmt"
import "os/exec"
func main() {
cmd := exec.Command("docker")
cmdOutput, err := cmd.Output()
if (err != nil) {
panic(err)
}
fmt.Println(string(cmdOutput))
}
But when I run this code as a result I get an empty string, which is stange since I get an output when I run docker command directly in a command line.
Moreover, this same code yields results just fine if I spawn other commands with it, for example ls.
What may be wrong here?
try using cmd.CombinedOutput()

Go command output by default to stdout?

I started learning and playing around with Go to see what it is like to make some more complex console/cli type tools instead of using shells or Python. I want to execute commands and display the output. I figured out how to print the output like this:
out, err := exec.Command("pwd").Output()
print(string(out))
Is there a way to execute the commands and have it default to stdout like a shell script, or do I need to make a helper function for this?
Update: After getting IntelliJ and the Go plugin, I poked around in the Go source and agree there is currently no way to do with without a helper method.
It is not possible to reuse a Cmd object as per this comment in the exec.go source code:
// A Cmd cannot be reused after calling its Run, Output or CombinedOutput
// methods.
I did incorporate the stdout option into my own helper, including other options like shell integration. I will try turn that into open source if I can make it useful. An interesting first day of Go.
The solution
Actually, it is pretty easy. You can set the stdout of the command to os.Stdout and Bob's your uncle:
package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command("pwd")
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
panic(err)
}
}
What's happening here?
By default, the output of a command is stored in a bytes.Buffer if cmd.Stdout is not set to another io.Writer. The call of cmd.Output() then runs the command and saves the output to said buffer.
Since os.Stdout implements io.Writer interface, we simply set cmd.Stdout to be os.Stdout. Now when .Run() is called, the output of the command gets written to the io.Writer defined in cmd.Stdout, which happens to be os.Stdout and the output gets written in the shell.
EDIT: As per comment, if all commands should write to os.Stdout, there of course is no way to prevent some helper. I'd do it like this:
package main
import (
"os"
"os/exec"
)
func CmdToStdout( c string ) (err error){
cmd := exec.Command(c)
cmd.Stdout = os.Stdout
err = cmd.Run()
return
}
func main() {
err := CmdToStdout("pwd")
if err != nil {
panic(err)
}
}
You have to create a helper if you need this often (and 5 lines looks too much). Based on the documentation this is a recommended way:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}

Getting output from `exec.Cmd` in "real-time"

This question is similar to Golang - Copy Exec output to Log except it is concerned with the buffering of output from exec commands.
I have the following test program:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("python", "inf_loop.py")
var out outstream
cmd.Stdout = out
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
fmt.Println(cmd.Wait())
}
type outstream struct{}
func (out outstream) Write(p []byte) (int, error) {
fmt.Println(string(p))
return len(p), nil
}
inf_loop.py, which the above refers to, simply contains:
print "hello"
while True:
pass
The go program hangs when I run it and doesn't output anything, but if I use os.Stdout instead of out then it outputs "hello" before it hangs. Why is there a discrepancy between the two io.Writers and how can it be fixed?
Some more diagnostic information:
When the loop is removed from inf_loop.py then "hello" is output from both programs, as expected.
When using yes as the program instead of the python script and outputting len(p) in outstream.Write then there is output, and the output is usually 16384 or 32768. This indicates to me that this is a buffering issue, as I originally anticipated, but I still don't understand why the outstream structure is being blocked by buffering but os.Stdout isn't. One possibility is that the behaviour is the result of the way that exec passes the io.Writer directly to os.StartProcess if it is an os.File (see source for details), otherwise it creates an os.Pipe() between the process and the io.Writer, and this pipe may be causing the buffering. However, the operation and possible buffering of os.Pipe() is too low-level for me to investigate.
Python buffers stdout by default. Try this program:
import sys
print "hello"
sys.stdout.flush()
while True:
pass
or run Python with unbuffered stdout and stderr:
cmd := exec.Command("python", "-u", "foo.py")
Note the -u flag.
You see different results when using cmd.Stout = os.Stdout because Python uses line buffering when stdout is a terminal.

Using Sudosh with Golang

my programs purpose is to do some tasks and then sudosh as a user all the way at the end.
However when running this with Go I get the sudosh: couldn't get your controlling terminal.
Is this because sudosh is looking for a controlling terminal and can't find it? Is this possible to do with Go? I am converting a Python script over to this Go program and it worked fine in Python.
import (
"github.com/codeskyblue/go-sh"
"fmt"
"os"
)
c, _ := sh.Command("sudo", "sudosh", "bob").Output()
fmt.Println(c)
os.Exit(0)
sudosh: couldn't get your controlling terminal.
The Fix
import "os/exec"
func sudosh(name string) {
c := exec.Command("/bin/sh", "-c", "sudo /path/to/sudosh " + name)
c.Stdin = os.Stdin
c.Stderr = os.Stderr
c.Stdout = os.Stdout
o := c.Run()
o=o
}
sudo2sh source shows that it is trying to call ttyname, which might not work in the context of a go-sh.Command calling exec.Command(cmd, args...).
You can test by calling ttyname directly, or by trying and implement a TTYname() function as in this thread.
You can also check the isatty function of go-termutil to debug the issue (as in this answer).
The bottom line is: if ttyname fails in a exec.Command session, sudosh will always return that error message.

Resources