Go : Correctly running external program with arguments - go

Good evening,
I'm working on converting some tools written in python to Go in order to better understand it.
I need the program to call an external .exe with some arguments in order for it to correctly format some data. In the windows shell I can do C:\path_to_exe\file.exe arg1 arg2 "C:\path_to_output\output.txt"
I believe the correct method to do this in Go would be using exec.Command, but I'm not getting any...meaningful results.
out, err := exec.Command("cmd", "C:\\path\\tools\\util\\Utility.exe C:\\file_Location \"select * from TABLE\" C:\\output_path\\output.txt").Output()
fmt.Printf("\n", string(out))
if err != nil {
println(" Error running decomp ", err)
}
This appears to be running command, as the output I am receiving is:
%!(EXTRA string=Microsoft Windows [Version 10.0.22000.739]
(c) Microsoft Corporation. All rights reserved.
Process finished with the exit code 0
Just for giggles I tried breaking up the arguments, but the same result was achieved
out, err := exec.Command("cmd", exPath, utilPath, statement, textOutputPath+"test.txt").Output()
I'm expecting the executed program to run, parse the correct file based on the input, and output the specified txt file. I am left with no .txt file, and the go program runs much faster then the parsing should take.
There must be something I'm missing, could someone please provide some insight on the correct usage of exec.Command, because every example I can find appears to show that this should work.

Why are you spawning cmd.exe and having it run your utility.exe?
You can just spawn utility on its own.
For instance, suppose you have two binaries, hello and say-hello living in the same directory, compiled from
hello.go → hello:
package main
import (
"fmt"
"os"
)
func main() {
argv := os.Args[1:]
if len(argv) == 0 {
argv = []string{"world"}
}
for _, arg := range argv {
fmt.Printf("Hello, %s!\n", arg)
}
}
say-hello.go → say-hello:
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
process := exec.Command("./hello", os.Args[1:]...)
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Stderr = os.Stderr
if err := process.Run(); err != nil {
fmt.Printf("Command failed with exit code %d\n", process.ProcessState.ExitCode())
fmt.Println(err)
}
}
You can then run the command:
$ ./say-hello Arawn Gywdion Sarah Hannah
And get back the expected
Hello, Arawn!
Hello, Gwydion!
Hello, Sarah!
Hello, Hannah!

It appears to be working correctly according to the outputs in your question.
A few suggestions:
It might be useful to print the command out as a string before running it, to check it's what you want.
You may find backticks useful when you have a string containing backslashes and quotation marks.
You have not supplied any format to fmt.Printf, hence the EXTRA in that output.
Using println to print the error will not stringify it, so use fmt.Printf for that too.
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("cmd", `C:\path\tools\util\Utility.exe C:\file_Location "select * from TABLE" C:\output_path\output.txt`)
fmt.Printf("%s\n", cmd.String())
out, err := cmd.Output()
fmt.Printf("%s\n", string(out))
if err != nil {
fmt.Printf(" Error running decomp %s\n", err)
}
}
Playground: https://go.dev/play/p/3t0aOxAZRtU

Related

Running os.execute to get the output

Im using the following code which run command against binary and need to provide output
if I run the command ftr get apps in the in my mac I got
[app1 apps2]
Now I copy the binary to the test data folder
and run the code below and I want to get the apps, currenlty there is no error but Im not getting also the data, what could be missing here?
Cmd := exec.Command("ftr", "get", "apps")
Cmd.Dir = "./testdata/"
err := Cmd.Start()
fmt.Println(err)
bytes, e := Cmd.Output()
fmt.Println(bytes, e)
You won't directly have an output, since the commands takes some time before it writes in stdout/stderr, so you need to basically wait for something to come out.
The way you can do it is by using bufio.NewScanner, like this:
package main
import (
"bufio"
"fmt"
"os/exec"
"strings"
)
func main() {
args := "get apps"
cmd := exec.Command("ftr", strings.Split(args, " ")...)
cmd.Dir = "./testdata/"
stdout, _ := cmd.StdoutPipe()
cmd.Start()
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
cmd.Wait()
}
If your command writes on stderr instead of stdout, you might need to use cmd.StderrPipe instead of cmd.Stdoutpipe in order to get the output.
Calling both Start and Output is redundant.
Output runs the command and returns its standard output.
Start is used to run the command asynchronously. Simply remove that call:
cmd := exec.Command("ftr", "get", "apps")
cmd.Dir = "./testdata/"
b, err := cmd.Output()
fmt.Println(string(b), err)

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

exec with double quoted argument

I want to execute find Windows command using exec package, but windows is doing some weird escaping.
I have something like:
out, err := exec.Command("find", `"SomeText"`).Output()
but this is throwing error because Windows is converting this to
find /SomeText"
Does anyone know why? How I can execute find on windows using exec package?
Thanks!
OK, it's a bit more complicated than you might have expected, but there is a solution:
package main
import (
"fmt"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command(`find`)
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.CmdLine = `find "SomeText" test.txt`
out, err := cmd.Output()
fmt.Printf("%s\n", out)
fmt.Printf("%v\n", err)
}
Unfortunately, although support for this was added in 2011, it doesn't appear to have made it into the documentation yet. (Although perhaps I just don't know where to look.)
FYI, running:
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("find", `"SomeText"`)
fmt.Printf("Path: %q, args[1]: %q\n", cmd.Path, cmd.Args[1])
}
playground
On unix gives:
Path: "/usr/bin/find", args[1]: "\"SomeText\""
And cross compiled to Windows and run on Win7 gives:
Path: "C:\\Windows\\system32\\find.exe", args[1]: "\"SomeText\""
Both look correct to me.
Adding out, err := cmd.Output() to the Windows cross-compile gives the following for fmt.Printf("%#v\%v\n", err, err):
&exec.ExitError{ProcessState:(*os.ProcessState)(0xc0820046a0)}
exit status 1
But I imagine that's just because find fails to find anything.

How to execute a simple Windows command in Golang?

How to run a simple Windows command?
This command:
exec.Command("del", "c:\\aaa.txt")
.. outputs this message:
del: executable file not found in %path%
What am I doing wrong?
I got the same error as you.
But dystroy is correct: You can't run del or any other command built into cmd because there is no del.exe file (or any other del-executable for that matter).
I got it to work with:
package main
import(
"fmt"
"os/exec"
)
func main(){
c := exec.Command("cmd", "/C", "del", "D:\\a.txt")
if err := c.Run(); err != nil {
fmt.Println("Error: ", err)
}
}
You need a Windows cmd to execute your dir command.
Try this :
cmd := exec.Command("cmd", "/C", "dir").Output()
(sorry, no Windows computer to check it right now)
Found another solution too. Create a batch file that contains the following: del c:\aaa.txt
Then call it like this:
exec.Command("c:\\del.bat").Run()
In case you need the output of cmd:
if c, err := exec.Command("cmd","/c","del","a.txt").CombinedOutput(); err != nil {
log.Fatal(err)
} else {
fmt.Printf("%s\n", c)
}
Ok let's see, according to the documentation, in windows, processes receive commands as a single line string and do some parsing of their own. Exec's Command function builds the command string by combining all arguments together using CommandLineToArgvW, that despite being the most common quoting algorithm doesn't work for every application. Applications like msiexec.exe and cmd.exe use an incompatible unquoting algorithm, hence the extra mile.
Heres a different example using powershell
package main
import (
"os/exec"
"fmt"
"log"
)
func main() {
out, err := exec.Command("powershell","remove-item","aaa.txt").Output()
if err != nil {
log.Fatal(err)
} else {
fmt.Printf("%s",out)
}
you can try use github.com/go-cmd/cmd module.
because golang can not use syscall by default.
example:
import (
"fmt"
"time"
"github.com/go-cmd/cmd"
)
func main() {
// Start a long-running process, capture stdout and stderr
findCmd := cmd.NewCmd("find", "/", "--name", "needle")
statusChan := findCmd.Start() // non-blocking
ticker := time.NewTicker(2 * time.Second)
// Print last line of stdout every 2s
go func() {
for range ticker.C {
status := findCmd.Status()
n := len(status.Stdout)
fmt.Println(status.Stdout[n-1])
}
}()
// Stop command after 1 hour
go func() {
<-time.After(1 * time.Hour)
findCmd.Stop()
}()
// Check if command is done
select {
case finalStatus := <-statusChan:
// done
default:
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
finalStatus := <-statusChan
}
c := exec.Command("cmd", "/C", "dir", "d:\\")
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Run()

Resources