Skip to content

Example: Executing Command Line

Hanjun Kim edited this page Mar 11, 2019 · 7 revisions

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.

Examples

You can find examples in example/cmdline.