What I want to do is exporting a file from my server via SFTP, in golang.
Here is the troubling code
cmd = exec.Command("sftp", "login#sftp.com", `INPUT
cd /some/path
put file.gz
quit
INPUT`)
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
fmt.Println(stderr.String())
os.Exit(1)
}
fmt.Println("done")
It gets to done but doesn't import file.gz.
I finally found the solution.
cmd = exec.Command("sftp", "login#sftp.com")
cmd.Stdin = strings.NewReader(`cd some/path
put file.gz
quit`)
cmd.Stderr = &stderr
err = cmd.Start()
if err != nil {
fmt.Println(stderr.String())
fmt.Println(err)
os.Exit(1)
}
err = cmd.Wait()
I set the commands I need to do once I'm in the STFP by setting cmd.Stdin
cmd.Start() and cmd.Wait() starts the command and stops when the whole command is done.
Related
Code in function to run a fzf against an input, while debugging i discovered my code doesn't return errors, this code runs successfully:
reader := strings.NewReader(listOutput.String())
r, w, _ := os.Pipe()
os.Stdout = w
cmd := exec.Command("fzf", "--multi")
cmd.Stdin = reader
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Couldn't call fzf: %v", err)
}
w.Close()
So i changed the command to something that doesn't exist, but the code still doesn't return "couldn't call command: command not found", just exits.
reader := strings.NewReader(listOutput.String())
r, w, _ := os.Pipe()
os.Stdout = w
cmd := exec.Command("idontexist")
cmd.Stdin = reader
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Couldn't call command: %v", err)
}
w.Close()
I don't have an idea what could be wrong.
cmd.Run() does return an error, and your if block gets properly executed, but since you change the standard output os.Stdout = w you just don't see the result on your console / terminal.
The fmt package writes to the standard output.
If you use the log package, you will see the error as the log package writes to the standard error (which you didn't change):
log.Printf("Couldn't call command: %v", err)
This will output something like (note the default log format includes the timestamp too):
2022/12/07 13:46:19 Couldn't call command: exec: "idontexist": executable file not found in $PATH
Or don't change the standard output.
Also do note that log.Println() and fmt.Println() do not require a format string. Do use log.Printf() and fmt.Printf() when you specify a format string and arguments.
I'm trying to open a new application, and then close the current application. I read that Command.Start() doesn't wait for the command to finish, but my program doesn't exit until fgl-updater.exe exits as well. I need it to close so that I can remove/replace it with the updated version.
func RunUpdater() {
wd, _ := os.Getwd()
cmd := exec.Command("cmd.exe", "/c", "start", wd+"/fgl-updater.exe")
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err := cmd.Start()
if err != nil {
fmt.Println("ERROR: ", err)
}
os.Exit(0)
}
Is this a Windows/cmd issue?
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 currently trying to execute a simple echo command in Golang on Linux. My code is the following:
cmd = exec.Command("echo", "\"foo 0x50\"", ">", "test.txt")
_, err = cmd.Output()
if err != nil {
fmt.Println(err)
}
But test.txt doesn't appear in my folder (even after compile and run the code). That not the first that that I use this method to execute commands and I never thought that I will be block on an echo command.
So how can I fix this code in order to have "foo 0x50" (with the quotes) in the test.txt?
You can redirect the stdout like this:
// Remove the redirect from command
cmd := exec.Command("echo", "\"foo 0x50\"")
// Make test file
testFile, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer outfile.Close()
// Redirect the output here (this is the key part)
cmd.Stdout = testFile
err = cmd.Start(); if err != nil {
panic(err)
}
cmd.Wait()
For anyone who wants to pass the complete command in oneline/string ,
func call(command string) {
cmd := exec.Command("sudo", "bash", "-c", command)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
fmt.Println(err)
}
err1 := cmd.Wait()
if err1 != nil {
fmt.Println(err1)
}
}
The Wait waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete. It closes the pipe after seeing the command exit.
Sources :
https://stackoverflow.com/a/43246464/9892358
https://stackoverflow.com/a/43246464/9892358
https://zetcode.com/golang/exec-command/
I'm trying to run a fairly simple bash command from my Go code. My program writes out an IPTables config file and I need to issue a command to make IPTables refresh from this config. This is very straightforward at the commandline:
/sbin/iptables-restore < /etc/iptables.conf
However, I can't for the life of me figure out how to issue this command with exec.Command(). I tried a few things to accomplish this:
cmd := exec.Command("/sbin/iptables-restore", "<", "/etc/iptables.conf")
// And also
cmd := exec.Command("/sbin/iptables-restore", "< /etc/iptables.conf")
No surprise, neither of those worked. I also tried to feed the filename into the command by piping in the file name to stdin:
cmd := exec.Command("/sbin/iptables-restore")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
io.WriteString(stdin, "/etc/iptables.conf")
That doesn't work either, no surprise. I can use stdin to pipe in the contents of the file, but this seems silly when I can just tell iptables-restore what data to go read. So how might I get Go to run the command /sbin/iptables-restore < /etc/iptables.conf?
first read this /etc/iptables.conf file content then write it to cmd.StdinPipe() like this:
package main
import (
"io"
"io/ioutil"
"log"
"os/exec"
)
func main() {
bytes, err := ioutil.ReadFile("/etc/iptables.conf")
if err != nil {
log.Fatal(err)
}
cmd := exec.Command("/sbin/iptables-restore")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(stdin, string(bytes))
if err != nil {
log.Fatal(err)
}
}
cmd := exec.Command("/usr/sbin/iptables-restore", "--binary", iptablesFilePath)
_, err := cmd.CombinedOutput()
if err != nil {
return err
}
return nil
this work fine on my Raspberry Pi3
The os/exec package does not invoke the system shell, nor does it implement the < redirection syntax typically handled by a shell.
Open the input file and use that file as stdin:
stdin, err := os.Open("/etc/iptables.conf")
if err != nil {
log.Fatal(err)
}
defer stdin.Close()
cmd := exec.Command("/sbin/iptables-restore")
cmd.Stdin = stdin // <-- use open file as stdin
result, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", result)