Executing a python script from Golang with arguments - go

I am trying to execute a python script ( which is used for accessing remote machines and run commands ) from Golang, it errors with "exit status 2"
out, err := exec.Command("/usr/local/opt/bin/python3.7", "/users/test.py -i 12.13.14.15 --cmd \"uptime && date\"").Output()
if err != nil {
fmt.Printf("%s", err)
} else {
fmt.Println("Command Successfully Executed")
output := string(out[:])
fmt.Println(output)
}
Output
exit status 2
thank you.

You are passing a single argument to the executable containing everything. Instead, you have to pass each argument separately:
out, err := exec.Command("/usr/local/opt/bin/python3.7", "/users/test.py", "-i", "12.13.14.15", "--cmd", "uptime && date").Output()

Related

Golang 'exec.Command' treat single quote parameter special?

I am refer to the post How to execute system command with unknown arguments? to run a jq command on my ubuntu shell.
Below is the code I tried
import (
"fmt"
"os/exec"
"sync"
"strings"
)
func exeCmd(cmd string, wg *sync.WaitGroup) {
fmt.Println("command is ",cmd)
// splitting head => g++ parts => rest of the command
parts := strings.Fields(cmd)
head := parts[0]
parts = parts[1:len(parts)]
out, err := exec.Command(head,parts...).Output()
if err != nil {
fmt.Printf("%s", err)
}
fmt.Printf("%s", out)
wg.Done() // Need to signal to waitgroup that this goroutine is done
}
func main() {
wg := new(sync.WaitGroup)
wg.Add(1)
x := []string{"jq '(.data.legacyCollection.collectionsPage.stream.edges|map({node:(.node|{url,firstPublished,headline:{default:.headline.default},summary})})) as $edges|{data:{legacyCollection:{collectionsPage:{stream:{$edges}}}}}' long-response.json > short-response.json"}
exeCmd(x[0], wg)
wg.Wait()
}
The output is as below, seems like the commans is correctly detected but shell returns exit status 3 which is "no such process" ?
command is jq '(.data.legacyCollection.collectionsPage.stream.edges|map({node:(.node|{url,firstPublished,headline:{default:.headline.default},summary})})) as $edges|{data:{legacyCollection:{collectionsPage:{stream:{$edges}}}}}' long-response.json > short-repsonse.json exit status 3
Anyone can help on this ?
What I want is a go function that can wrapper and run bash command line the same way as I do on Linux shell
PS: the jq command I tried above works pretty well when I paste it on my Linux shell
Tried somethine else: deleted the single quote in my jq command and my command got executed with output I expect - a parsed json file
but still, I got a exist status 2 , anyone can explain
why the single quote in my command line affects how G parse the command ?
why I still go exist 2 after my shell commands complete?
The program executes the jq command, not a shell. The jq command does not understand the shell syntax passed to the command (the quotes and I/O redirection).
Use the following code to run the command with stdout redirected to short-response.json.
cmd := exec.Command("jq",
"(.data.legacyCollection.collectionsPage.stream.edges|map({node:(.node|{url,firstPublished,headline:{default:.headline.default},summary})})) as $edges|{data:{legacyCollection:{collectionsPage:{stream:{$edges}}}}}",
"long-response.json")
f, err := os.Create("short-response.json")
if err != nil {
log.Fatal(err)
}
defer f.Close()
cmd.Stdout = f // set stdout to short-response.json
err = cmd.Run()
if err != nil {
log.Fatal(err)
}

Command that works in terminal doesn't work with go exec.Command [duplicate]

This question already has an answer here:
calling command with some arguments works but not with others but works from console
(1 answer)
Closed 1 year ago.
I'm trying to attach to a go-ethereum node from a go script:
accountInfo:= fmt.Sprintf("attach %v --exec 'eth.getBalance(eth.accounts[0])'", connect)
//x, err := exec.Command("geth", accountInfo).Output() // returns 'Fatal: Unable to attach to remote geth: no known transport for URL scheme "c"'
x, err := exec.Command("geth", accountInfo).Output() // returns "invalid command: "attach ws://127.0.0.1:8101...." <--- rest of the metaData string
if err != nil {
fmt.Println(err)
}
This command works perfectly fine in terminal but it keeps telling me it's invalid when running it like this. This is making me go nuts.
From the os/exec documentation:
Unlike the "system" library call from C and other languages, the os/exec package intentionally does not invoke the system shell and does not expand any glob patterns or handle other expansions, pipelines, or redirections typically done by shells.
Since the arg ...string parameter of exec.Command() isn't processed by a shell, each argument is handed to the command exactly as specified. In your case, the entire content of metaData is provided to geth as a single argument.
You should instead create a slice of strings, each containing a single argument. And then provide that slice as the arg parameter using the ... notation.
Here's an example demonstrating this, using the uname command:
package main
import (
"fmt"
"os/exec"
)
func main() {
command := "uname"
argsString := "--kernel-name --machine"
argsSlice := []string{"--kernel-name", "--machine"}
// Equivalent command:
// $ uname "--kernel-name --machine"
fmt.Println("exec.Command(command, argsString)")
stringOutput, err := exec.Command(command, argsString).CombinedOutput()
if err != nil {
fmt.Printf("Failed: %s\n", err)
}
fmt.Printf("Output:\n%s\n", stringOutput)
// Equivalent command:
// $ uname --kernel-name --machine
fmt.Println("exec.Command(command, argsSlice)")
sliceOutput, err := exec.Command(command, argsSlice...).CombinedOutput()
if err != nil {
fmt.Printf("Failed: %s", err)
}
fmt.Printf("Output:\n%s\n", sliceOutput)
}
And it's otuput:
$ go run main.go
exec.Command(command, argsString)
Failed: exit status 1
Output:
uname: unrecognized option '--kernel-name --machine'
Try 'uname --help' for more information.
exec.Command(command, argsSlice)
Output:
Linux x86_64

Start detached command with redirect to file

I'm trying to start a command in a detached process so that it can continue after go program exits. I need to redirect the output of the command to a file.
What I need is something like this:
func main() {
command := exec.Command("/tmp/test.sh", ">", "/tmp/out")
if err := command.Start(); err != nil {
fmt.Fprintln(os.Stderr, "Command failed.", err)
os.Exit(1)
}
fmt.Println("Process ID:", command.Process.Pid)
}
Obviously such redirect doesn't work. As I immediately exit from the program after starting the long running command, I cannot open a file and bind it to the Stdout.
Is there any way to achieve such a redirect?
You may start a shell which executes your command / app, and you may redirect its output to a file. The shell will continue to run and execute your script / app even if your Go app exits.
Example:
cmd := exec.Command("sh", "-c", "/tmp/test.sh > /tmp/out")
if err := cmd.Start(); err != nil {
panic(err)
}
fmt.Println("Process ID:", cmd.Process.Pid)
Test it with this simple Go app (replace /tmp/test.sh with the name of the executable binary you compile this into):
package main
import ("fmt"; "time")
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("%d.: %v\n", i, time.Now())
time.Sleep(time.Second)
}
}
This app simply prints a line to the standard output once every second. You can see how the output file is being written e.g. with tail -f /tmp/out.
Note that you may use other shells to execute your scripts to your liking (and to what the test.sh script dictates).
For example to use bash:
cmd := exec.Command("/bin/bash", "-c", "/tmp/test.sh > /tmp/out")
// rest is unchanged
Note that the command to be executed by the shell is passed as a single string argument, and it is not broken down into multiple as you would do it if you were to execute it directly in the command prompt.
Maybe you can try to use this: https://stackoverflow.com/a/28918814/2728768
Opening a file (and os.File implements io.Writer), and then passing it as the command.Stdout could do the trick:
func main() {
command := exec.Command("./tmp/test.sh")
f, err := os.OpenFile("/tmp/out", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("error opening file: %v", err)
}
defer f.Close()
// On this line you're going to redirect the output to a file
command.Stdout = f
if err := command.Start(); err != nil {
fmt.Fprintln(os.Stderr, "Command failed.", err)
os.Exit(1)
}
fmt.Println("Process ID:", command.Process.Pid)
}
Not sure this could be a viable solution for your case. I've tried it locally and it seems working... remember that your user should be able to create/update the file.

Run Multiple Exec Commands in the same shell golang

I'm having trouble figuring out how to run multiple commands using the os/exec package. I've trolled the net and stackoverflow and haven't found anything that works for me case. Here's my source:
package main
import (
_ "bufio"
_ "bytes"
_ "errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
)
func main() {
ffmpegFolderName := "ffmpeg-2.8.4"
path, err := filepath.Abs("")
if err != nil {
fmt.Println("Error locating absulte file paths")
os.Exit(1)
}
folderPath := filepath.Join(path, ffmpegFolderName)
_, err2 := folderExists(folderPath)
if err2 != nil {
fmt.Println("The folder: %s either does not exist or is not in the same directory as make.go", folderPath)
os.Exit(1)
}
cd := exec.Command("cd", folderPath)
config := exec.Command("./configure", "--disable-yasm")
build := exec.Command("make")
cd_err := cd.Start()
if cd_err != nil {
log.Fatal(cd_err)
}
log.Printf("Waiting for command to finish...")
cd_err = cd.Wait()
log.Printf("Command finished with error: %v", cd_err)
start_err := config.Start()
if start_err != nil {
log.Fatal(start_err)
}
log.Printf("Waiting for command to finish...")
start_err = config.Wait()
log.Printf("Command finished with error: %v", start_err)
build_err := build.Start()
if build_err != nil {
log.Fatal(build_err)
}
log.Printf("Waiting for command to finish...")
build_err = build.Wait()
log.Printf("Command finished with error: %v", build_err)
}
func folderExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
I want to the command like I would from terminal. cd path; ./configure; make
So I need run each command in order and wait for the last command to finish before moving on. With my current version of the code it currently says that ./configure: no such file or directory I assume that is because cd path executes and in a new shell ./configure executes, instead of being in the same directory from the previous command. Any ideas?
UPDATE I solved the issue by changing the working directory and then executing the ./configure and make command
err = os.Chdir(folderPath)
if err != nil {
fmt.Println("File Path Could not be changed")
os.Exit(1)
}
Still now i'm curious to know if there is a way to execute commands in the same shell.
If you want to run multiple commands within a single shell instance, you will need to invoke the shell with something like this:
cmd := exec.Command("/bin/sh", "-c", "command1; command2; command3; ...")
err := cmd.Run()
This will get the shell to interpret the given commands. It will also let you execute shell builtins like cd. Note that this can be non-trivial to substitute in user data to these commands in a safe way.
If instead you just want to run a command in a particular directory, you can do that without the shell. You can set the current working directory to execute the command like so:
config := exec.Command("./configure", "--disable-yasm")
config.Dir = folderPath
build := exec.Command("make")
build.Dir = folderPath
... and continue on like you were before.

Executing a Bash Script from Golang

I am trying to figure out a way to execute a script (.sh) file from Golang. I have found a couple of easy ways to execute commands (e.g. os/exec), but what I am looking to do is to execute an entire sh file (the file sets variables etc.).
Using the standard os/exec method for this does not seem to be straightforward: both trying to input "./script.sh" and loading the content of the script into a string do not work as arguments for the exec function.
for example, this is an sh file that I want to execute from Go:
OIFS=$IFS;
IFS=",";
# fill in your details here
dbname=testDB
host=localhost:27017
collection=testCollection
exportTo=../csv/
# get comma separated list of keys. do this by peeking into the first document in the collection and get his set of keys
keys=`mongo "$host/$dbname" --eval "rs.slaveOk();var keys = []; for(var key in db.$collection.find().sort({_id: -1}).limit(1)[0]) { keys.push(key); }; keys;" --quiet`;
# now use mongoexport with the set of keys to export the collection to csv
mongoexport --host $host -d $dbname -c $collection --fields "$keys" --csv --out $exportTo$dbname.$collection.csv;
IFS=$OIFS;
from the Go program:
out, err := exec.Command(mongoToCsvSH).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("output is %s\n", out)
where mongoToCsvSH can be either the path to the sh or the actual content - both do not work.
Any ideas how to achieve this?
For your shell script to be directly runnable you have to:
Start it with #!/bin/sh (or #!/bin/bash, etc).
You have to make it executable, aka chmod +x script.
If you don't want to do that, then you will have to execute /bin/sh with the path to the script.
cmd := exec.Command("/bin/sh", mongoToCsvSH)
This worked for me
func Native() string {
cmd, err := exec.Command("/bin/sh", "/path/to/file.sh").Output()
if err != nil {
fmt.Printf("error %s", err)
}
output := string(cmd)
return output
}
You need to execute /bin/sh and pass the script itself as an argument.
This allows you to pass the arguments as well as get the output of the script in the Stdout or Stderr.
import (
"github.com/sirupsen/logrus"
"os"
"os/exec"
)
func Execute(script string, command []string) (bool, error) {
cmd := &exec.Cmd{
Path: script,
Args: command,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
c.logger.Info("Executing command ", cmd)
err := cmd.Start()
if err != nil {
return false, err
}
err = cmd.Wait()
if err != nil {
return false, err
}
return true, nil
}
Calling example:
command := []string{
"/<path>/yourscript.sh",
"arg1=val1",
"arg2=val2",
}
Execute("/<path>/yourscript.sh", command)

Resources