目录

Go在windows上调用本地进程传参时的一个天坑


目录

go在windows上exec.Command调用本地进程在传参的时候有一个天坑,举个栗子来说正常来说一般代码会这么写

1
2
3
 cmdLine := "notepad.exe " + `"D:\Program Files\Notepad++\session.xml"`
 cmd := exec.Command("cmd.exe", "/c", cmdLine)
 err := cmd.Run()

我们期望在拉起notepad的时候是会正常解析参数并打开xml文件的,但是,你可以尝试运行一下这段代码,结果并不是我们想的这样!

notepad在打开的时候会报一个非法路径,这是怎么回事呢?打开processexp看一下传入的参数如下

1
notepad.exe  \"D:\Program Files\Notepad++\session.xml\"

看到在双引号中强制加入了转义符,一开始我以为这是string里强制加入的,想到这做法也太奇怪了,强制转string为byte[]后,一个一个对字符发现原字符串中根本就没有出现转义符,搜遍国内外只有提问的人,却没找到有效的解决办法。

想了一下,那这问题必然就出在Command解析中了,查看源码中有一段说明

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.

也就是说,针对cmd参数加的引号参数会有不同的逻辑,必须在SysProcAttr.CmdLine中写入原始参数了,但是Args留空,又会导致SysProcAttr值为nil,所以简单赋值也是不行的,那么正确的代码实现如下

1
2
3
4
 cmdLine := "notepad.exe " + `"D:\Program Files\Notepad++\session.xml"`
 cmd := exec.Command("cmd.exe")
 cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/c " + cmdLine}
 err := cmd.Run()

运行一下,果然notepad正确打开了文件

4 Comments

Anonymous

。。。

Anonymous

感谢大佬啊!!!,查遍国内外,倒你这里才解决,折腾了一天一晚了,https://github.com/golang/go/issues/15566 有人都说不会再使用go语言了,这么做是为不熟悉命令行参数的人准备?本末倒置了

golang的windows库中处理这一段确实有点脱裤子放屁的感觉

Anonymous

以下是评论

s-h-a-d-o-w

commented


  on 21 May 2018

For simple cmd.exe invocations (where no escaping required)

Considering that probably half of the billions of child processes spawned each day on Windows have something to do with cmd and spaces in paths are nothing unusual since at least the year 2000, I find this curiously optimistic. And odd that the member CmdLine isn't even listed here: https://golang.org/pkg/syscall/#SysProcAttr

Also no example for how to use it to be found aside from e.g. here: https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773 I probably won't use Go any more myself but still wanted to note that this is obviously a very common use case (simply check the stars on nvm-windows if you don't know what nvm is), so it would be for your own benefit to document this instead of dismiss it.

But that's just my two cents... Go has survived without this for 8 years already after all. :)