Go: execute bash script - bash

How do I execute a bash script from my Go program? Here's my code:
Dir Structure:
/hello/
public/
js/
hello.js
templates
hello.html
hello.go
hello.sh
hello.go
cmd, err := exec.Command("/bin/sh", "hello.sh")
if err != nil {
fmt.Println(err)
}
When I run hello.go and call the relevant route, I get this on my console:
exit status 127
output is
I'm expecting ["a", "b", "c"]
I am aware there is a similar question on SO: Executing a Bash Script from Golang, however, I'm not sure if I'm getting the path correct. Will appreciate help!

exec.Command() returns a struct that can be used for other commands like Run
If you're only looking for the output of the command try this:
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)
}

You can also use CombinedOutput() instead of Output(). It will dump standard error result of executed command instead of just returning error code.
See:
How to debug "exit status 1" error when running exec.Command in Golang

Check the example at http://golang.org/pkg/os/exec/#Command
You can try by using an output buffer and assigning it to the Stdout of the cmd you create, as follows:
var out bytes.Buffer
cmd.Stdout = &out
You can then run the command using
cmd.Run()
If this executes fine (meaning it returns nil), the output of the command will be in the out buffer, the string version of which can be obtained with
out.String()

Related

Go : Correctly running external program with arguments

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

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

How can I run the dir command with golang?

Here is my code:
package main
import (
"bytes"
"fmt"
"io"
"os/exec"
)
func runCommand(command string) io.Writer{
cmdName := "cmd.exe"
cmdArgs := []string{"/c", command}
fmt.Println("Running command: " + command)
cmd := exec.Command(cmdName, cmdArgs...)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.Run()
return cmd.Stdout
}
func main(){
fmt.Println(runCommand("dir")) // Prints the output of dir for the current directory
fmt.Println(runCommand("dir C:\\")) // Prints nothing
fmt.Println(runCommand("dir C:\\Users\\")) //Prints the output of dir for the users directory
fmt.Println(runCommand("dir C:\\..\\")) // Prints the output of dir for the C drive (What I want)
}
I'm expecting that when I execute dir C:\ That I would get the output as if I had ran in in a windows command prompt. Instead I get nothing. Intestingly, any other path when running dir works just fine. I can even see C:\ If I instead execute C:\..\ Why is this? I don't understand why this happens, and every other windows command I have given it works fine.
First of all, never ignore errors. The call to cmd.Run() returns an error, you should always check it:
if err := cmd.Run(); err != nil {
fmt.Printf(os.Stderr, "%v", err)
}
Try that and you might see why your command is failing.
Without knowing the error, it's hard to help fixing your problem, but I'd guess you need to split the string command into several fields and append them to cmdArgs. When running runCommand("dir C:\\"), your cmdArgs is actually []string{"/c", "dir C:\\"), I think it should be []string{"/c", "dir", "C:\\"}. Take a look at the function strings.Split(string, string), it might help you. But that's just a guess, we need to know the exact error message you're having for a proper solution :)

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

Resources