Familiarizing myself with Golang here and i am trying to execute shell commands, i need to chmod for any .pem file so i decided to use the wildcard *
func main() {
cmd := exec.Command( "chmod", "400", "*.pem" )
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
if err := cmd.Run(); err != nil {
fmt.Println( "Error:", err )
}
I keep get this error while executing:
chmod: cannot access '*.pem': No such file or directory
Error: exit status 1
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.
So here, * will not be expanded. As a workaround, you should use next to make it work:
cmd := exec.Command("sh", "-c", "chmod 400 *.pem" )
Here is another approach to change a file permission using os package
filepath.WalkDir(".", func(filePath string, f fs.DirEntry, e error) error {
if e != nil {
log.Fatal(e)
}
if filepath.Ext(f.Name()) == ".pem" {
err := os.Chmod(filePath, 0400)
if err != nil {
log.Fatal(err)
}
fmt.Println(fmt.Sprintf("Successfully changed file permission, file: %s", filePath))
}
return nil
})
Complete code can be found at https://play.golang.org/p/x_3WsYg4t52
Related
I am trying to write a file from a bash command into a file in Go.
Note there are several reasons for using Go over bash here: I have some more logic such as parsing configuration files, I would like to run that code for multiple DBs in parallele and finally performing some more complex data manipulation after.
dumpStr := fmt.Sprintf("pg_dump -U %s -h %s %s | gzip", DbUserName, DbHost, DbName)
cmd := exec.Command("bash", "-c", dumpStr)
cmd.Env = append(cmd.Env, "PGPASSWORD="+DbPassword)
outfile, err := os.Create(DbName + ".gz")
if err != nil {
panic(err)
}
outfile = cmd.Stdout
defer outfile.Close()
err = cmd.Start()
if err != nil {
panic(err)
}
cmd.Wait()
However, I am getting an emtpy result.
I am getting data if I am executing dumpStr from the CLI but not from that code...
What am I missing?
As Flimzy said, you're not capturing the output of pg_dump. You can do that with Go, or you can use pg_dump-s --file. It can also compress with --compress so no need to pipe to gzip. Then there's no need for bash and you can avoid shell quoting issues.
cmd := exec.Command(
"pg_dump",
"--compress=9",
"--file="+DbName + ".gz",
"-U"+DbUserName,
"-h"+DbHost,
DbName,
)
log.Print("Running pg_dump...")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
Much simpler and more secure.
For illustration here's how you'd do it all in Go.
Use Cmd.StdoutPipe to get an open IO reader to pg_dump's stdout. Then use io.Copy to copy from stdout to your open file.
#Peter points out that since Cmd.Stdout is an io.Reader it's simpler to assign the open file to cmd.Stdout and let cmd write to it directly.
// Same as above, but no --file.
cmd := exec.Command(
"pg_dump",
"--compress=9",
"-U"+DbUserName,
"-h"+DbHost,
DbName,
)
// Open the output file
outfile, err := os.Create(DbName + ".gz")
if err != nil {
log.Fatal(err)
}
defer outfile.Close()
// Send stdout to the outfile. cmd.Stdout will take any io.Writer.
cmd.Stdout = outfile
// Start the command
if err = cmd.Start(); err != nil {
log.Fatal(err)
}
log.Print("Waiting for command to finish...")
// Wait for the command to finish.
if err = cmd.Wait(); err != nil {
log.Fatal(err)
}
In addition, you're only checking if the command started, not if it successfully ran.
From the docs for Cmd.Start.
Start starts the specified command but does not wait for it to complete.
The Wait method will return the exit code and release associated resources once the command exits.
You're checking cmd.Start for an error, but not cmd.Wait. Checking the error from cmd.Start only means the command started. If there is an error while the program is running you won't know what it is.
You need to actually use the output of your command. You're not doing that. To do so, use the StdoutPipe method, then you can copy the stdout from your program, into your file.
I'm trying to run a command in Golang, but it looks like it loses the exit code because the err is nil:
func runCommand() []byte, error {
cmd := exec.Command("/bin/bash", "-c", "KUBECONFIG=/tmp/.kube/config helm version")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
stdOut, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stdOut)
if err != nil {
return nil, err
}
if err := cmd.Wait(); err != nil {
return nil, err
}
fmt.Println(string(bytes))
return bytes, nil
}
This return nil, even though the command returns with exit code != 0.
If I type:
$> /bin/bash -c KUBECONFIG=/tmp/.kube/config helm version
$<
$> echo $?
$< 0
If I type:
$> /bin/bash -c 'KUBECONFIG=/tmp/.kube/config helm version'
$< ...connection refused
$> echo $?
$< 1
So I tried to wrap the command in single quote:
cmd := exec.Command("/bin/bash", "-c", "'KUBECONFIG=/tmp/.kube/config helm version'")
but then I get:
/bin/bash: KUBECONFIG=/tmp/.kube/config helm version: No such file or directory
(needless to say that /tmp/.kube/config is there, but I don't think the no such file or directory refers to that anyway).
What am I doing wrong?
Thanks
UPDATE: turns out I got it wrong. In fact I had two commands attempted and for some reason I was sure the one failing was the one I mentioned above, when instead the second command was exiting with a status code different from 0. The code works as expected and the err is not nil in case of exit code != 0. Sorry about that.
Seems like you should be able to get it with exec.ExitError, see exec package. Note that you may need Go 1.12. Here's a runnable example (but it won't give you realistic output at the go playground):
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
)
func main() {
cmd := exec.Command(`/bin/bash`, `-c`, `FOO=bar ls /foo`)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
stdOut, err := cmd.StdoutPipe()
if err != nil {
fmt.Println("Error 1")
}
if err := cmd.Start(); err != nil {
fmt.Println("Error 2")
}
bytes, err := ioutil.ReadAll(stdOut)
if err != nil {
fmt.Println("Error 3")
}
if err := cmd.Wait(); err != nil {
fmt.Println("Error 4")
if exitError, ok := err.(*exec.ExitError); ok {
fmt.Printf("Exit code is %d\n", exitError.ExitCode())
}
}
fmt.Println(string(bytes))
}
On my system that prints:
$ go run main.go
ls: cannot access '/foo': No such file or directory
Error 4
Exit code is 2
If that doesn't work for you, maybe it's worth following #JimB's suggestion and invoking helm directly? The Go standard library should support pipes as well:
It wraps os.StartProcess to make it easier to remap stdin and stdout, connect I/O with pipes, and do other adjustments.
(exec package)
How can I make the exec.Command command call a command from another file?
func main() {
fmt.Println("Iniciando...")
command := exec.Command("java -version")
command.Dir = "."
output, err := command.Output()
if err != nil {
fmt.Println("Erro: ", err)
}
fmt.Printf("%s", output)
}
Erro: exec: "java -version": executable file not found in $PATH
Each argument needs to be in its own separate string. Try exec.Command("java", "-version")
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.
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.