‘The half minute which we daily devote to the winding-up of our watches is an exertion of labour almost insensible; yet, by the aid of a few wheels, its effect is spread over the whole twenty-four hours.’

So you need kill (or do something else to) something in Linux

Benjamin Slade

(Or, what if you can’t just click the bad thing with the skull until it dies?) #

Sometimes you need to kill something in Linux. Sometimes it makes sense to use some sort interactive process monitor, like the process table in Plasma’s System Monitor, or top or htop or bottom or some other sort of top.1 (Or, if you’re in an X11 environment rather than a Wayland one, you could use xkill.2)

killing with killall #

You can often get by with killall, e.g., if you want to kill all running Firefox applications:

killall firefox

killing with pkill #

Or you could use pkill (which has a number of options) in much the same way.

These sorts of approaches don’t always work. Sometimes (this is true in Guix3 a lot) processes are named in ways that killall or pkill don’t match.

listing processes and process ids with ps #

You can list running processes in Linux with ps -ef, and this will show you all running processes. It’s too much, obviously, there are a lot of things going on in your system. So, you can filter it by “piping” the output of ps -ef through grep, e.g., ps -ef | grep -i emacs, which might show you something like:

emacsomancer    15188 26384  0 21:05 ?        00:00:02 /gnu/store/5vkx1cf1d2k9dj974vgd77yx0fdis284-emacs-pdf-tools-1.1.0/bin/epdfinfo
emacsomancer    23294 26384  0 21:33 ?        00:00:00 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously nil)
emacsomancer    23550  2250  0 21:34 pts/4    00:00:00 grep --color=auto -i emacs
emacsomancer    26384     1  5 19:47 ?        00:05:42 /home/emacsomancer/.guix-home/profile/bin/emacs --daemon --debug-init
emacsomancer    26513 26384  0 19:47 ?        00:00:03 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26514 26384  0 19:47 ?        00:00:02 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26515 26384  0 19:47 ?        00:00:02 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26516 26384  0 19:47 ?        00:00:01 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26517 26384  0 19:47 ?        00:00:02 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26518 26384  0 19:47 ?        00:00:02 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26519 26384  0 19:47 ?        00:00:01 /home/emacsomancer/.guix-home/profile/bin/emacs --quick --batch --load /home/emacsomancer/.emacs.d/eln-cache/30.0.93-e51375a4/el-job-child-8a892b0e-c7b5e3df.eln --eval (el-job-child--work #'org-node-parser--collect-dangerously t)
emacsomancer    26750 23962  0 19:48 tty8     00:00:00 /home/emacsomancer/.guix-home/profile/bin/emacsclient -c
emacsomancer    26761 23962  0 19:48 tty8     00:00:00 /home/emacsomancer/.guix-home/profile/bin/emacsclient -c -e (mu4e)

Since you’ve piped ps through grep, you’re missing the explanatory column headers row (because that row doesn’t contain the string “emacs” and so has been filtered out by grep), which are:

UID        PID  PPID  C STIME TTY          TIME CMD

So, then, picking one of the entries above, let’s line them up:

UID             PID    PPID  C STIME TTY      TIME     CMD
emacsomancer    26384     1  5 19:47 ?        00:05:42 /home/emacsomancer/.guix-home/profile/bin/emacs --daemon --debug-init

The UID is just the name of “user” who executed the process (probably you). The CMD is what the process actually is, which is important. The other important piece here is the PID, which is the Process ID or Processor identifier, for with this you can have your system kill (or do other things) to a very specific process (usually an application, or a process it’s spun off). The other fields aren’t relevant for us here.4

kill (or, how to kill things nicely) #

With the PID, you can do various things with a process, such as kill. This could be a simple kill 26384 to kill the emacs --daemon process.

Sometimes, processes don’t want to die and a simple kill won’t work. kill is equivalent to kill -TERM or kill -15, which essentially nicely ask the process to stop. This is often good, because if the process has some sort of “exit things” it does, like saving backups of open files, it will do those things first before stopping.

But sometimes the reason you’re in the command line in the first place is because a process is hung, and so the simple kill won’t do any good.

kill -9 (or, what if politely killing doesn’t work) #

In that case you can do kill -9, which is “the unsafe way of brutally murdering a process. It’s equivalent to pulling the power cord, and may cause data corruption."5 This often not what you want to do, but is useful for when you need to force something to stop immediately or when it’s not responding to polite kill's. (Though kill -9 / SIGKILL won’t kill zombies, because they’re dead already and are just waiting for their parent processes to reap them.)

Advanced pkill (or, what if there are too many things to kill one by one?) #

If you recall the ps -ef | grep -i emacs output from above, there are often a bunch of associated processes. I’ve often ended up having to try to run kill (or kill -9) on a bunch of PIDs one by one until finally the application shut down (cases where killall didn’t work for one reason or other).

We can instead use our process lister (ps -ef) with piping to pkill. E.g., like this in the case of killing all processes named “emacs”:

ps -ef | pkill -f emacs

Careful, because, especially if the “name” is short, it might be a substring of other running processes. (I.e., you probably don’t want to try something like ps -ef | pkill -f e. It would kill emacs --daemon, true, but it would also kill any other process with an e in it.)

You can check first by piping ps -ef through pgrep:

ps -ef | pgrep -l emacs # show the list of all the processes (and their names) to be killed first

to see a list of the names of the processes that would be killed (for our emacs example).

Or, if you need a bit more information, do instead:

ps -ef | pgrep -a emacs # show the list of all the processes (and their full command line, not just name) to be killed first

to see the full command line (including but not limited to the process’s name).

And, just like kill above, pkill is by default the “polite kill”. If you’re dealing with stubborn processes, you can add a -9 to order SIGKILL, e.g.:

ps -ef | pkill -9 -f emacs

(If you had a completely hung Emacs, say.)

What if you need to do something other than killing a bunch of things? #

This is all well and good if all you need to do is kill, but sometimes there are other things to do.

For instance, I finally got Mullvad VPN‘s own graphical client to work on Guix,6 but for some reason the graphical interface split tunnelling doesn’t work for me on Guix (usually the Mullvad interface would allow you open a specific application outside of the VPN tunnel (i.e. on your regular connection)). Fortunately, Mullvad also has a command-line interface and one can add currently running applications to be excluded from the VPN tunnel (this is also useful even if your Mullvad GUI split-tunnelling is working for not having to shut down and re-open applications if you want them excluded from your VPN tunnel). But it does it by PID, e.g.:

mullvad split-tunnel add 26384

(if we were excluding the emacs --daemon PID from the above example from the VPN tunnel, say.)

One of the obvious things one might want to exclude from a VPN tunnel is a particular browser (e.g., there’s a site that doesn’t like the VPN, so you open up a second browser outside of the VPN to use to access that site).

But browsers often involve a number of processes, and I found myself having to manually run the mullvad split-tunnel add command on ten different PIDs, and check mullvad.net each time to see if that was the one I needed or not.

This is both time-consuming and frustrating and, just like kill'ing, we can instead do it at scale.

Now, the mullvad split-tunnel command itself doesn’t have a built-in facility to either add processes by name or more than one in a single shot, but we can write a loop over a list of PIDs and have the loop execute mullvad split tunnel add ... for each one.

In Bash (“the Bourne Again SHell”), if you wanted to exclude Firefox from the Mullvad VPN tunnel, you could do:

for pid in $(pgrep -u $(id -u) -f "firefox"); do mullvad split-tunnel add "$pid"; done

Just like for pkill, you can double-check before what would be added to the tunnel exclusion by doing one of:

ps -ef | pgrep -l firefox # show the list of all the processes (and their names) to be killed first
ps -ef | pgrep -a firefox # show the list of all the processes (and their full command line, not just name) to be killed first

If you’re using Fish shell (“FInally, a command line SHell for the 90s”), instead of the Bash line above, you would use:

for pid in $(pgrep -u $(id -u) -f "firefox"); mullvad split-tunnel add "$pid"; end

But what if you don’t want to remember arcane incantations? #

I don’t, or, well, won’t. I can probably find it in my shell history once I’ve run it on a machine, but we can do better than that.

We can instead add a function to the shell.

For Bash, you would add to your ~/.bash_profile:

function mullvad-split-tunnel-by-name() {
  for pid in $(pgrep -u "$(id -u)" -f "$1")
  do
      mullvad split-tunnel add "$pid"
      printf "added to split tunnel: %s\n" "$(ps -p "$pid" -o command= | awk '{print $1}')"
  done
}

(You don’t need the printf bit if you don’t want, but this way you’ll see a proper list of the names of all of the excluded processes, which the mullvad split-tunnel add command doesn’t do by itself.)

Then (once your .bash_profile is source'ed)7, you could just enter the following in the terminal to have all firefox processes excluded from the Mullvad VPN tunnel:

mullvad-split-tunnel-by-name firefox

(You can choose a shorter name for your function than mullvad-split-tunnel-by-name of course, but for me such is fine with TAB completion.)

And similarly for other things you wanted to exclude from the VPN tunnel (e.g., mullvad-split-tunnel-by-name chromium).

For Fish, you would add a new file to your ~/.config/fish/functions/ directory (say ~/.config/fish/functions/mullvad-functions.fish) with the following content:

function mullvad-split-tunnel-by-name --description 'adds all matching instances to mullvads split tunnel'
  for pid in $(pgrep -u $(id -u) -f $argv)
      mullvad split-tunnel add "$pid"
      printf "added to split tunnel: %s\n" "$(ps -p $pid -o command= | awk '{print $1}')"
  end
end

(Again, the printf line is optional. And again you should source it or restart your shell.)

A couple of other useful mullvad split-tunnel functions #

These can be defined in similar fashion to the above mullvad-split-tunnel-by-name: one for removing things from the VPN excluded list, and for one for showing what’s currently on the list by name. (since mullvad split-tunnel list just spits back a list of raw PIDs, which isn’t very helpful for identifying what’s actually being split tunnelled….)

  • removing processes from exclude list

    Like mullvad-split-tunnel-by-name, but for removing excluded processes (i.e., re-including them in VPN tunnel):

    • In Bash:

      function mullvad-remove-process-by-name() {
         for pid in $(pgrep -u "$(id -u)" -f "$1")
         do
             mullvad split-tunnel delete "$pid"
             printf "removed from split tunnel: %s\n" "$(ps -p "$pid" -o command= | awk '{print $1}')"
         done
      }
      
    • In Fish:

    function mullvad-remove-process-by-name --description 'remove all matching instances to mullvads split tunnel'
      for pid in $(pgrep -u $(id -u) -f $argv)
          mullvad split-tunnel delete "$pid"
          printf "removed from split tunnel: %s\n" "$(ps -p $pid -o command= | awk '{print $1}')"
      end
    end
    
  • listing processes in exclude list by name rather than PID

    Checking on what’s currently split out from the VPN tunnel: this provide a listing of processes currently excluded from the VPN tunnel by name (in a couple of options):

    • In Bash:
    # list full command line info associated with each PID in list
    function mullvad-list-excluded-processes-by-long-name() {
        mullvad_vpn_exclude_array=( $(mullvad split-tunnel list) )
        mvre='^[0-9]+$'
        printf "Processes excluded from VPN tunnel:\n\n"
        for key in "${!mullvad_vpn_exclude_array[@]}"
        do
            if  [[ ${mullvad_vpn_exclude_array[$key]} =~ $mvre ]]
            then
                printf "PID %s = %s\n" "${mullvad_vpn_exclude_array[$key]}" "$(ps -p "${mullvad_vpn_exclude_array[$key]}" -o command=)"
            fi
        done
    }
    
    # list just process name info associated with each PID in list
    function mullvad-list-excluded-processes-by-short-name() {
        mullvad_vpn_exclude_array=( $(mullvad split-tunnel list) )
        mvre='^[0-9]+$'
        printf "Processes excluded from VPN tunnel:\n\n"
        for key in "${!mullvad_vpn_exclude_array[@]}"
        do
            if  [[ ${mullvad_vpn_exclude_array[$key]} =~ $mvre ]]
            then
                printf "PID %s = %s\n" "${mullvad_vpn_exclude_array[$key]}" "$(ps -p "${mullvad_vpn_exclude_array[$key]}" -o comm=)"
            fi
        done
    }
    
    • Similarly for Fish:
      function mullvad-list-excluded-processes-by-long-name --description 'lists excluded processes in mullvads split tunnel by long command name'
          printf "Processes excluded from VPN tunnel:\n"
          for key in $(mullvad split-tunnel list)
              if string match -qr '^[0-9]+$' -- "$key"
                   printf "PID %s: %s\n" "$key" "$(ps -p $key -o command=)"
              end
          end
      end
      
      function mullvad-list-excluded-processes-by-short-name --description 'lists excluded processes in mullvads split tunnel by short process name'
          echo "Processes excluded from VPN tunnel:"
          for key in $(mullvad split-tunnel list)
              if string match -qr '^[0-9]+$' -- "$key"
                   printf "PID %s: %s\n" "$key" "$(ps -p $key -o comm=)"
              end
          end
      end
      

Beyond tunnelling and killing #

These techniques — including defining various named speciality functions (in whatever shell) — could obviously be adapted for other similar cases of wanting to run a command on multiple processes sharing (all or part of) a name, whenever you find you’re tired of wasting time manually mucking about with heaps of PIDs.


  1. For list of good Linux system top's and related, see, e.g., https://github.com/luong-komorebi/Awesome-Linux-Software#system-info--monitoring ↩︎

  2. The Wikipedia page for xkill notes: ‘Xkill has been cited as an example of a program with a simple and appealing user interface. Its mode of operation has been summed up as “Just click the bad thing with the skull and it dies."’ ↩︎

  3. A Scheme-based package manager+Linux distribution (Wikipedia: Guix). See elsewhere on this blog: #guix. ↩︎

  4. The PPID is the Process ID of parent process. You can see that the PID we’re looking at here is 26384, if you look at the longer list of the output of ps -ef | grep -i emacs above, you can see that this is the PPID of a number of the other listed processes, because they were started by this emacs --daemon process. (—which itself has a PPID of 1 because I started it from the terminal myself, and that seems to count as being started by Process 1, the first process started during the booting of the system, which is usually the init system, presumably because it’s the ancestor of all other processes and “adopts” any “orphaned” or otherwise apparently unsupervised processes.) ↩︎

  5. Quote from: https://stackoverflow.com/a/43725403/570251. -9 is equivalent to -SIGKILL, on which see here, whereas the plain kill or kill -15 is -SIGTERM, on which see here. ↩︎

  6. A working package definition for the Mullvad VPN desktop client on Guix is here. Using Mullvad on Guix has been a longtime bugbear for me — so much so that I once wrote my frontend for it in Common Lisp: Volemad. ↩︎

  7. I.e., source ~/.bash_profile. ↩︎