Golang – 按名称终止进程

huangapple go评论92阅读模式
英文:

Golang - kill process by name

问题

如果你只知道进程的名称,想要用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]*')

我已经使用exec.Command()来执行它了,但如果有更好的方法,我想避免使用它。

英文:

What would be an effective way to kill a process with Go code if you only know the process name? I see some functions provided by the os package like:

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

Is there a good/common practice to get the pid without having to execute commands and then parse the output?

I have found a way to get back the pid using a command like the following:

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

and I have used it with exec.Command() but I would like to avoid it if there is a better approach.

答案1

得分: 9

运行外部命令可能是实现这个功能的最佳方法。然而,以下代码至少在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上运行。

英文:

Running external commands is probably the best way to do this. However, the following code runs on Ubuntu at least as long as you are the owner of the process to kill.

// killprocess project main.go
package main
import (
&quot;bytes&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;io/ioutil&quot;
&quot;log&quot;
&quot;os&quot;
&quot;path/filepath&quot;
&quot;strconv&quot;
&quot;strings&quot;
)
// 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/&lt;pid&gt;/status file. If
// the name matches the name in the argument the process with the corresponding
// &lt;pid&gt; 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&#39;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/&lt;pid&gt;/status.
if strings.Count(path, &quot;/&quot;) == 3 {
if strings.Contains(path, &quot;/status&quot;) {
// Let&#39;s extract the middle part of the path with the &lt;pid&gt; and
// convert the &lt;pid&gt; into an integer. Log an error if it fails.
pid, err := strconv.Atoi(path[6:strings.LastIndex(path, &quot;/&quot;)])
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 &quot;Name: theProcess&quot;.
// 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, &#39;\n&#39;)])
if name == args[1] {
fmt.Printf(&quot;PID: %d, Name: %s will be killed.\n&quot;, pid, name)
proc, err := os.FindProcess(pid)
if err != nil {
log.Println(err)
}
// Kill the process
proc.Kill()
// Let&#39;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(&quot;Usage: killprocess &lt;processname&gt;&quot;)
}
fmt.Printf(&quot;trying to kill process \&quot;%s\&quot;\n&quot;, args[1])
err := filepath.Walk(&quot;/proc&quot;, findAndKillProcess)
if err != nil {
if err == io.EOF {
// Not an error, just a signal when we are done
err = nil
} else {
log.Fatal(err)
}
}
}

It's just an example that certainly can be improved. I wrote this for Linux and tested the code on Ubuntu 15.10. It will not run on Windows.

答案2

得分: 9

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

我已经实施了各种解决方案来做到这一点几个月了,但出于某种原因,我花了这么长时间才找到gopsutil。这是一个第三方库,对你来说可能是个优势或劣势,但它在我们的跨平台项目中运行得非常完美。下面的示例将终止第一个与指定名称匹配的进程,但可以很容易地修改为终止所有具有相同名称的进程。

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
}

优雅终止

该库还支持通过向进程发送自定义信号来进行优雅终止。

// 使用以下代码
err = p.SendSignal(syscall.SIGINT)

// 而不是
err = p.Kill()
英文:

Cross-Platform (3rd party) Solution

I've implemented various solutions to do this for months now, and for some reason it took me that long to find gopsutil. It is a 3rd party library and that may or may not be a deal breaker for you, but it has worked flawlessly for our cross-platform projects. The following example will kill the first process with the matching name, but it can easily be adapted to kill all processes with the name.

import &quot;github.com/shirou/gopsutil/v3/process&quot;
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(&quot;process not found&quot;)
}

With Context Support

As an added bonus, the library also supports context cancellation on all process related operations including process queries, and killing the process.

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
}

Graceful Termination

The library also supports graceful termination by sending your own signal to the process.

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

答案3

得分: 8

我最终使用了类似以下的代码:

// 使用以下命令以sudo权限运行命令
// echo "sudo_password" | sudo -S [command]

_, err := exec.Command("sh", "-c", "echo '"+sudopassword+"' | sudo -S pkill -SIGINT my_app_name").Output()

if err != nil {
    // ...
} else {
    // ...
}

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

根据维基百科的说明:

  • SIGINT

    当用户希望中断进程时,控制终端会向进程发送SIGINT信号。通常通过按下Ctrl+C来触发,但在某些系统上,可以使用"delete"字符或"break"键。

  • SIGKILL

    发送SIGKILL信号给进程会立即终止(杀死)它。与SIGTERM和SIGINT不同,该信号无法被捕获或忽略,并且接收进程在接收到该信号后无法执行任何清理操作。以下情况除外:

英文:

I finally used something like the following:

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

I used the SIGINT signal to gracefully stop the app.

From wikipedia:

  • SIGINT

    The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. This is typically initiated by pressing Ctrl+C, but on some systems, the "delete" character or "break" key can be used.

  • SIGKILL

    The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal. The following exceptions apply:

答案4

得分: 0

你可以使用Go语言通过进程ID来终止一个进程,所以真正的问题是如何通过进程名获取进程ID。以下是一个针对Windows的示例代码:

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)
}

你可以在这里找到有关CreateToolhelp32Snapshot函数的更多信息:https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot

英文:

You can can already kill a process by process ID with Go, so the real question
here is getting the process ID from the process name. Here is example for
Windows:

package main
import (
&quot;fmt&quot;
&quot;golang.org/x/sys/windows&quot;
)
// 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, &amp;p)
if e != nil { return 0, e }
if windows.UTF16ToString(p.ExeFile[:]) == name {
return p.ProcessID, nil
}
}
return 0, fmt.Errorf(&quot;%q not found&quot;, name)
}
func main() {
n, e := processID(&quot;WindowsTerminal.exe&quot;)
if e != nil {
panic(e)
}
println(n)
}

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

答案5

得分: 0

对于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://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill

英文:

For Windows:

You can use below method. Pass process name which you want to terminate.

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

Ref: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill

huangapple
  • 本文由 发表于 2016年12月9日 20:24:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/41060457.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定