Golang - 按名称杀死进程
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
是否有 good/common 实践来获取 pid
而无需执行命令然后解析输出?
我找到了一种使用如下命令取回 pid 的方法:
echo $(ps cax | grep myapp | grep -o '^[ ]*[0-9]*')
我有 但如果有更好的方法,我想避免它。
运行 外部命令可能是执行此操作的最佳方法。不过下面的代码运行s就Ubuntu至少只要你是进程的所有者就kill了。
// 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 上 运行。
我终于使用了类似下面的东西:
// `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 启动,但在某些系统上,可以使用 "delete" 字符或 "break" 键。
SIGKILL
SIGKILL 信号被发送到进程以使其立即终止(kill)。与SIGTERM和SIGINT不同,这个信号不能被捕获或忽略,接收进程在收到这个信号后不能执行任何clean-up。以下例外情况适用:
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)
}
https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot
跨平台(第 3 方)解决方案
几个月来我已经实施了各种解决方案来做到这一点,但出于某种原因,我花了这么长时间才找到 gopsutil。它是一个第 3 方库,对您来说可能会或可能不会成为交易破坏者,但它在我们的跨平台项目中完美运行。以下示例将终止具有匹配名称的第一个进程,但它可以很容易地修改为终止具有该名称的所有进程。
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()
对于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
如果你只知道进程名,用 Go 代码杀死进程的有效方法是什么?我看到 os
包提供的一些功能,例如:
func FindProcess(pid int) (*Process, error)
func (p *Process) Kill() error
func (p *Process) Signal(sig Signal) error
是否有 good/common 实践来获取 pid
而无需执行命令然后解析输出?
我找到了一种使用如下命令取回 pid 的方法:
echo $(ps cax | grep myapp | grep -o '^[ ]*[0-9]*')
我有
运行 外部命令可能是执行此操作的最佳方法。不过下面的代码运行s就Ubuntu至少只要你是进程的所有者就kill了。
// 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 上 运行。
我终于使用了类似下面的东西:
// `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 启动,但在某些系统上,可以使用 "delete" 字符或 "break" 键。
SIGKILL
SIGKILL 信号被发送到进程以使其立即终止(kill)。与SIGTERM和SIGINT不同,这个信号不能被捕获或忽略,接收进程在收到这个信号后不能执行任何clean-up。以下例外情况适用:
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)
}
https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot
跨平台(第 3 方)解决方案
几个月来我已经实施了各种解决方案来做到这一点,但出于某种原因,我花了这么长时间才找到 gopsutil。它是一个第 3 方库,对您来说可能会或可能不会成为交易破坏者,但它在我们的跨平台项目中完美运行。以下示例将终止具有匹配名称的第一个进程,但它可以很容易地修改为终止具有该名称的所有进程。
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()
对于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