【问题标题】:Go os/exec Command.Start() twice in a rowGo os/exec Command.Start() 连续两次
【发布时间】:2026-02-11 09:05:02
【问题描述】:

我正在尝试使用 Go 的 os/exec Command()模拟按键,有时我想在 快速继任。我正在使用exec.Command 调用"xte",“key XF86AudioPlay”,它可以在 Linux 操作系统上暂停音乐。虽然Command 可以Start() Run() 没问题,但如果我再次尝试执行,我得到一个错误:

exec: already started

我尝试在执行后立即使用Process.Kill(),以释放它,但这会使执行首先无法工作。我从这里得到了这个想法:Terminating a Process Started with os/exec in Golang

我的代码使用switch 并相应地调用此暂停函数,但我将简单分享我编写的代码的基础,以case 作为示例函数:

cmd := exec.Command("xte", "key XF86AudioPlay")
//...
func Pause() {
        err := cmd.Start() // or cmd.Run()
        if err != nil {
            fmt.Println(err)
        }
        err = cmd.Process.Kill()
        if err != nil {
            fmt.Printf("Failed to kill: %s", err)
        }
}

所以,回顾一下,我成功调用了一次,但在成功调用 Pause() 后,我收到来自 cmd.Start()/Run() 的错误,内容为:exec: already started

我也尝试过lower,即使用syscall,但遇到了一些麻烦。我试过了:

args[0] = "xte"
args[1] = "key"
args[2] = "XF86AudioPlay"
//... Pause():
            err := syscall.Exec("/bin", args, os.Environ())
        if err != nil {
            fmt.Println(err)
        }

这里我遇到了permission denied 错误,即使以超级用户 (sudo) 身份运行也是如此。

我应该如何继续调用此Command(),然后将其释放以立即召回?还是我与syscall 走在正确的轨道上?

编辑 因此,作为 Amd 和 Son Bui 州的解决方案是创建 Command 每次我打算调用它时,基本上将分配 cmd := exec.Command()inside 我的 @987654344 @方法。

【问题讨论】:

    标签: go exec system-calls


    【解决方案1】:

    正如type Cmd struct { Docs 所说:

    Cmd 在调用其 Run、Output 或 CombinedOutput 后​​不能被重用 方法。


    1- 像这样使用两个单独的exec.Command
    此外,您可能需要在运行之间进行一些延迟(模拟两个单独的击键):

    package main
    
    import (
        "fmt"
        "os"
        "os/exec"
        "time"
    )
    
    func main() {
        Pause()
        fmt.Println("Once")
        time.Sleep(100 * time.Millisecond)
        Pause()
        fmt.Println("Twice")
    }
    func Pause() {
        cmd := exec.Command("xte", "key XF86AudioPlay")
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        err := cmd.Run()
        if err != nil {
            fmt.Println(err)
        }
    }
    

    2- 你可以运行一次:

    你可以使用这样的东西(未经测试):

    xte 'key XF86AudioPlay' 'key XF86AudioPlay'
    

    并考虑为 xte 命令添加一个短暂的延迟(模拟两个单独的击键):

    xte 'key XF86AudioPlay' 'usleep 100000' 'key XF86AudioPlay'
    

    像这样:

    package main
    
    import (
        "fmt"
        "os"
        "os/exec"
    )
    
    func main() {
        Pause()
        fmt.Println("Once")
    }
    func Pause() {
        cmd := exec.Command("xte", `key XF86AudioPlay`, `usleep 100000`, `key XF86AudioPlay`)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        err := cmd.Run()
        if err != nil {
            fmt.Println(err)
        }
    }
    

    见:

    https://askubuntu.com/questions/499926/why-do-these-xte-commands-work-in-terminal-but-not-when-bound-with-xbindkeys

    http://manpages.ubuntu.com/manpages/wily/man1/xte.1.html

    http://wiki.robotz.com/index.php/Linux_Tools_to_Remap_Keys_and_Mouse_Buttons


    我希望这会有所帮助。

    【讨论】:

      【解决方案2】:

      从源码看:

      cmd 结构:(https://golang.org/src/os/exec/exec.go 第 99 行)

      // Process is the underlying process, once started.
      Process *os.Process
      

      Start 函数中(https://golang.org/src/os/exec/exec.go 第 327 行) :

      if c.Process != nil {
          return errors.New("exec: already started")
      }
      

      所以你只能使用 cmd.Start 一次。如果你想多次使用,你可以创建新的 Cmd 或者一次运行多个命令,例如:

      cmd := exec.Command("/bin/sh", "-c", "command1; command2; command3; ...")
      

      【讨论】: