【问题标题】:Golang - kill process by nameGolang - 按名称杀死进程
【发布时间】:2016-12-09 12:24:55
【问题描述】:

如果您只知道进程名称,那么用 Go 代码杀死进程的有效方法是什么?我看到os 包提供的一些功能,例如:

func FindProcess(pid int) (*Process, error)
func (p *Process) Kill() error
func (p *Process) Signal(sig Signal) error

有没有一种好的/常见的做法来获取pid 而不必执行命令然后解析输出?

我找到了一种使用如下命令取回 pid 的方法:

  • echo $(ps cax | grep myapp | grep -o '^[ ]*[0-9]*')

我有used it with exec.Command(),但如果有更好的方法,我想避免它。

【问题讨论】:

标签: go process command kill


【解决方案1】:

运行外部命令可能是最好的方法。但是,至少只要您是要杀死的进程的所有者,以下代码就可以在 Ubuntu 上运行。

// killprocess project main.go
package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

// args holds the commandline args
var args []string

// findAndKillProcess walks iterative through the /process directory tree
// looking up the process name found in each /proc/<pid>/status file. If
// the name matches the name in the argument the process with the corresponding
// <pid> will be killed.
func findAndKillProcess(path string, info os.FileInfo, err error) error {
    // We just return in case of errors, as they are likely due to insufficient
    // privileges. We shouldn't get any errors for accessing the information we
    // are interested in. Run as root (sudo) and log the error, in case you want
    // this information.
    if err != nil {
        // log.Println(err)
        return nil
    }

    // We are only interested in files with a path looking like /proc/<pid>/status.
    if strings.Count(path, "/") == 3 {
        if strings.Contains(path, "/status") {

            // Let's extract the middle part of the path with the <pid> and
            // convert the <pid> into an integer. Log an error if it fails.
            pid, err := strconv.Atoi(path[6:strings.LastIndex(path, "/")])
            if err != nil {
                log.Println(err)
                return nil
            }

            // The status file contains the name of the process in its first line.
            // The line looks like "Name: theProcess".
            // Log an error in case we cant read the file.
            f, err := ioutil.ReadFile(path)
            if err != nil {
                log.Println(err)
                return nil
            }

            // Extract the process name from within the first line in the buffer
            name := string(f[6:bytes.IndexByte(f, '\n')])

            if name == args[1] {
                fmt.Printf("PID: %d, Name: %s will be killed.\n", pid, name)
                proc, err := os.FindProcess(pid)
                if err != nil {
                    log.Println(err)
                }
                // Kill the process
                proc.Kill()

                // Let's return a fake error to abort the walk through the
                // rest of the /proc directory tree
                return io.EOF
            }

        }
    }

    return nil
}

// main is the entry point of any go application
func main() {
    args = os.Args
    if len(args) != 2 {
        log.Fatalln("Usage: killprocess <processname>")
    }
    fmt.Printf("trying to kill process \"%s\"\n", args[1])

    err := filepath.Walk("/proc", findAndKillProcess)
    if err != nil {
        if err == io.EOF {
            // Not an error, just a signal when we are done
            err = nil
        } else {
            log.Fatal(err)
        }
    }
}

这只是一个当然可以改进的例子。我为 Linux 编写了此代码,并在 Ubuntu 15.10 上测试了代码。它不会在 Windows 上运行。

【讨论】:

    【解决方案2】:

    我终于用了类似下面的东西:

    // `echo "sudo_password" | sudo -S [command]`
    // is used in order to run the command with `sudo`
    
    _, err := exec.Command("sh", "-c", "echo '"+ sudopassword +"' | sudo -S pkill -SIGINT my_app_name").Output()
    
    if err != nil {
        // ...
    } else {
        // ...
    }
    

    我使用SIGINT 信号优雅地停止了应用程序。

    来自wikipedia

    • SIGINT

      当用户希望中断进程时,SIGINT 信号由其控制终端发送到进程。这通常通过按 Ctrl+C 启动,但在某些系统上,可以使用“删除”字符或“中断”键。

    • SIGKILL

      SIGKILL 信号被发送到进程以使其立即终止(终止)。与 SIGTERM 和 SIGINT 相比,这个信号不能被捕获或忽略,并且接收进程在接收到这个信号时不能执行任何清理。以下例外情况适用:

    【讨论】:

    • 但是如果我们不知道 pid 如何杀死进程呢?
    • 上面的代码 sn-p 完全符合您的要求。你不知道pid,但你必须知道你想杀死的可执行文件的name。例如这里的信号发送到my_app_name
    【解决方案3】:

    跨平台(第三方)解决方案

    几个月来,我已经实施了各种解决方案来执行此操作,出于某种原因,我花了很长时间才找到gopsutil。它是一个 3rd 方库,对您来说可能会或可能不会破坏交易,但它在我们的跨平台项目中完美运行。以下示例将杀死第一个具有匹配名称的进程,但它可以很容易地调整为杀死具有该名称的所有进程。

    import "github.com/shirou/gopsutil/v3/process"
    
    func KillProcess(name string) error {
        processes, err := process.Processes()
        if err != nil {
            return err
        }
        for _, p := range processes {
            n, err := p.Name()
            if err != nil {
                return err
            }
            if n == name {
                return p.Kill()
            }
        }
        return fmt.Errorf("process not found")
    }
    

    具有上下文支持

    作为一个额外的好处,该库还支持所有进程相关操作的上下文取消,包括进程查询和终止进程。

    func KillAllProcessesCtx(ctx context.Context, name string) error {
        processes, err := process.ProcessesWithContext(ctx)
        if err != nil {
            return err
        }
        for _, p := range processes {
            n, err := p.NameWithContext(ctx)
            if err != nil {
                return err
            }
            if n == name {
                err = p.KillWithContext(ctx)
                if err != nil {
                    return err
                }
            }
        }
        return nil
    }
    

    优雅终止

    该库还支持通过向进程发送您自己的信号来优雅终止。

    // Do this
    err = p.SendSignal(syscall.SIGINT)
            
    // Instead of this
    err = p.Kill()
    

    【讨论】:

      【解决方案4】:

      您已经可以使用 Go 通过进程 ID 杀死一个进程,所以真正的问题 这里是从进程名称中获取进程 ID。这是示例 窗户:

      package main
      
      import (
         "fmt"
         "golang.org/x/sys/windows"
      )
      
      // unsafe.Sizeof(windows.ProcessEntry32{})
      const processEntrySize = 568
      
      func processID(name string) (uint32, error) {
         h, e := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
         if e != nil { return 0, e }
         p := windows.ProcessEntry32{Size: processEntrySize}
         for {
            e := windows.Process32Next(h, &p)
            if e != nil { return 0, e }
            if windows.UTF16ToString(p.ExeFile[:]) == name {
               return p.ProcessID, nil
            }
         }
         return 0, fmt.Errorf("%q not found", name)
      }
      
      func main() {
         n, e := processID("WindowsTerminal.exe")
         if e != nil {
            panic(e)
         }
         println(n)
      }
      

      https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot

      【讨论】:

        【解决方案5】:

        对于 Windows:

        您可以使用以下方法。传递您要终止的进程名称。

        func killProcessByName(procname string) int {
            kill := exec.Command("taskkill", "/im", procname, "/T", "/F")
            err := kill.Run()
            if err != nil {
                return -1
            }
            return 0
        }
        

        参考:https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill

        【讨论】:

          猜你喜欢
          • 2011-02-25
          • 2011-06-19
          • 2017-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-21
          • 2018-02-28
          • 1970-01-01
          相关资源
          最近更新 更多