13

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.

Community
  • 1
  • 1
tgogos
  • 23,218
  • 20
  • 96
  • 128
  • 1
    Possible duplicate of [List of currently running process in Golang](http://stackoverflow.com/questions/9030680/list-of-currently-running-process-in-golang) – I159 Dec 09 '16 at 14:02
  • There is no way but to execute an external command. – Nadh Dec 09 '16 at 18:49

5 Answers5

9

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

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.

Peter Gloor
  • 917
  • 6
  • 19
9

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

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()
Clark McCauley
  • 1,342
  • 5
  • 16
8

I finally used something like the following:

// `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 {
    // ...
}

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:

tgogos
  • 23,218
  • 20
  • 96
  • 128
  • But how to kill process if we do not know pid? – Priyanka Feb 13 '18 at 13:06
  • 1
    The above code snippet does exactly what you are asking for. You don't know the `pid` but you have to know the `name` of the executable that you want to kill. For example, here the signal is sent to `my_app_name` – tgogos Feb 13 '18 at 13:14
0

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 (
   "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

Zombo
  • 1
  • 62
  • 391
  • 407
0

For Windows:

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

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

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

RDX
  • 409
  • 6
  • 22