-
Notifications
You must be signed in to change notification settings - Fork 6
Example: Executing Command Line
You probably know how to execute external commands in Go.
Correct, it's os/exec
that does the job.
Here's a simple example using it:
package main
import "os/exec"
func main() {
if err := exec.Command("notepad", "foo.txt"); err != nil {
panic(err)
}
}
This code runs notepad.exe with argument "foo.txt"
.
You can see a Notepad window pops up and ask you whether to create a new file with name foo.txt or not.
Okay then, is it possible to pass a string argument for filename that contains spaces? Try it yourself:
if err := exec.Command("notepad", "foo bar baz.txt"); err != nil {
panic(err)
}
It works well. Actually, os/exec
automatically quotes arguments under the hood.
So most of the time, simply passing string arguments to exec.Command*
function is enough.
But if you look carefully the documentation for exec.Command
, you can find this note:
On Windows, processes receive the whole command line as a single string and do their own parsing. Command combines and quotes Args into a command line string with an algorithm compatible with applications using CommandLineToArgvW (which is the most common way). Notable exceptions are msiexec.exe and cmd.exe (and thus, all batch files), which have a different unquoting algorithm. In these or other similar cases, you can do the quoting yourself and provide the full command line in SysProcAttr.CmdLine, leaving Args empty.
It says that you need to quote the full command line yourself at some time.
Keeping that in mind, let's think about this situation.
You have a full command line from arbitrary source.
You wish to run this full command line as-is.
How can you do that with os/exec
? It gets tricky.
I've done it like this. Instead of using exec
package,
I used CreateProcess
and passed lpApplicationName
a NULL
.
Then passing lpCommandLine
my command line, it worked well.
But I noticed that in Go's standard package, there was CmdLine
field
inside syscall.SysProcAttr
structure. It seems that I could utilize this field.
Unfortunately these code below didn't work:
#1:
cmd := exec.Command("")
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: `notepad "foo bar.txt"`}
if err := cmd.Run(); err != nil {
panic(err)
}
#2:
cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: `notepad "foo bar.txt"`}
if err := cmd.Run(); err != nil {
panic(err)
}
Oops, what are we missing? So I filed an issue: https://github.com/golang/go/issues/29841
And thankfully I got the answer. Here's what it should be done:
cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: fmt.Sprintf(`/c "%s"`, `notepad "foo bar.txt"`)}
if err := cmd.Run(); err != nil {
panic(err)
}
It works well. I hope this trick to be documented offically.
You can find examples in example/cmdline.