在 Go 中,为什么 exec.Command() 失败但 os.StartProcess() 成功启动 "winget.exe"?
In Go, why does exec.Command() fail but os.StartProcess() succeed launching "winget.exe"?
exec.Command()
用于执行 C:\Windows\System32\notepad.exe
- 但是
exec.Command()
无法执行 C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
。失败并显示错误消息:
exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist
- 但是,
os.StartProcess()
可用于执行 C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
谁能告诉我为什么?
此代码片段无效。 winget.exe
未启动。
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
cmd := exec.Command(wingetPath, "--version")
err := cmd.Start()
fmt.Println(err)
// exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist
但这行得通:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
_, err := os.StartProcess(wingetPath, []string{wingetPath, "--version"}, procAttr)
fmt.Println(err)
// <nil>
转到版本:
> go version
go version go1.18 windows/amd64
Golang 中的错误
很明显,这是 Go
的 Windows 实现中的一个错误,并且已在 GitHub 上多次提交 - 我能找到的最早的是 issue这是几年前提交的。
错误是由于 exec.Command()
内部 uses os.Stat()
which does not read files with reparse points 正确造成的。 os.Lstat()
可以。
Windows 商店应用使用 App Execution Aliases, which are essentially zero-byte files with reparse points. This post 有一些额外的细节。
解决方法
- 解决方法是使用
os.StartProces()
- 较低级别 API,使用起来可能有点痛苦,尤其是与 os.Exec()
相比时。
重要:在os.StartProcess()
中,argv slice在新进程中会变成os.Args
,所以一般应该把程序名作为第一个传递参数:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
/*
To redirect IO, pass in stdin, stdout, stderr as required
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
*/
args = []string { "install", "git.git" }
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
proc, err := os.StartProcess(wingetPath,
append([]string{wingetPath}, arg...), procAttr)
fmt.Println(err) // nil
exec.Command()
用于执行C:\Windows\System32\notepad.exe
- 但是
exec.Command()
无法执行C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
。失败并显示错误消息:exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist
- 但是,
os.StartProcess()
可用于执行C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
谁能告诉我为什么?
此代码片段无效。 winget.exe
未启动。
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
cmd := exec.Command(wingetPath, "--version")
err := cmd.Start()
fmt.Println(err)
// exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist
但这行得通:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
_, err := os.StartProcess(wingetPath, []string{wingetPath, "--version"}, procAttr)
fmt.Println(err)
// <nil>
转到版本:
> go version
go version go1.18 windows/amd64
Golang 中的错误
很明显,这是 Go
的 Windows 实现中的一个错误,并且已在 GitHub 上多次提交 - 我能找到的最早的是 issue这是几年前提交的。
错误是由于 exec.Command()
内部 uses os.Stat()
which does not read files with reparse points 正确造成的。 os.Lstat()
可以。
Windows 商店应用使用 App Execution Aliases, which are essentially zero-byte files with reparse points. This post 有一些额外的细节。
解决方法
- 解决方法是使用
os.StartProces()
- 较低级别 API,使用起来可能有点痛苦,尤其是与os.Exec()
相比时。
重要:在os.StartProcess()
中,argv slice在新进程中会变成os.Args
,所以一般应该把程序名作为第一个传递参数:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\WindowsApps\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
/*
To redirect IO, pass in stdin, stdout, stderr as required
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
*/
args = []string { "install", "git.git" }
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
proc, err := os.StartProcess(wingetPath,
append([]string{wingetPath}, arg...), procAttr)
fmt.Println(err) // nil