Skip to content

Instantly share code, notes, and snippets.

@deeplook
Last active April 20, 2016 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deeplook/5e087bf392ec6b538df60ced46185f93 to your computer and use it in GitHub Desktop.
Save deeplook/5e087bf392ec6b538df60ced46185f93 to your computer and use it in GitHub Desktop.
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
A tool for signaling processes found also by their command-line invocation.
Sometimes you want to terminate a process based on some specific substring
in the command that was entered in a terminal to start it, e.g. here you
might want to terminate only a process like this that is running a Python
SimpleHTTPServer started with::
python -m SimpleHTTPServer 8095 > server.log 2>&1 &
But ``kill``, ``killall`` and ``pkill`` do not permit selecting a process by
matching or searching a substring in the entire command used to create it.
And even ``killall -m`` matches only the process name (in the example above
something like ``/usr/bin/python``), but not any other part of the full
command-line.
This is what the function signal_process() in this module allows to do.
For convenience, some of the options from ``kill`` and ``killall`` are
reimplemented here, too.
This module depends on ``psutil`` and ``tabulate``, both easyily installable
with ``pip``.
Usage:
$ pterm.py --details
$ pterm.py --list
$ pterm.py -i --name python2.7 --cmdline_search SimpleHTTPServer --signal-name SIGTERM
>>> from pterm import signal_process
>>> signal_process(interactive=True, name='python2.7',
cmdline_search='SimpleHTTPServer',
signal_name='SIGTERM')
Remaining killall options (for reference only):
-e Use the effective user ID instead of the (default) real user ID for matching
processes specified with the -u option.
-m Match the argument procname as a (case sensitive) regular expression against the
names of processes found. CAUTION! This is dangerous, a single dot will match
any process running under the real UID of the caller.
-s Show only what would be done, but do not send any signal.
-t tty Limit potentially matching processes to those running on the specified tty.
-z Do not skip zombies. This should not have any effect except to print a few
error messages if there are zombie processes that match the specified pattern.
Other examples (for reference only):
$ killall -s python
kill -TERM 20591
kill -TERM 18042
kill -TERM 14016
kill -TERM 13833
kill -TERM 6862
kill -TERM 6848
kill -TERM 2718
$ killall -l
HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP
TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE
9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGINFO 30) SIGUSR1 31) SIGUSR2
"""
import re
import os
import sys
import argparse
import psutil
import tabulate
# create map of known signals e.g. 'SIGTERM': 15, ...
known_signals = {
k: v
for (k, v) in psutil.signal.__dict__.items()
if k.startswith('SIG') and type(v) in (int, long)
}
# add shortcut entries, e.g. 'TERM': 15, ..., and '15': 15, ...
temp = {}
for key in known_signals:
if key.startswith('SIG'):
temp[key[3:]] = known_signals[key]
temp[str(known_signals[key])] = known_signals[key]
known_signals.update(temp)
del key, temp
def signal_process(signal_name=None, name=None, username=None,
cmdline_search=None, cmdline_match=None,
details=False, interactive=False, inclusive=False):
"""
Send a signal to a process selected by different criteria, incl. cmdline.
:param signal_name: signal name, symbolic like 'SIGTERM', 'TERM' or numeric like '15'
:type signal_name: string
:param name: process name
:type name: string
:param username: username
:type username: string
:param cmdline_search: regular expression used for searching command-lines
:type cmdline_search: string
:param cmdline_match: regular expression used for matching command-lines
:type cmdline_match: string
:param details: print detailed information about the processes matched, but do not send any signal,
default: False
:type details: Boolean
:param interactive: ask for confirmation before sending a signal to a process,
default: False
:type interactive: Boolean
:param inclusive: include this process, default: False
:type inclusive: Boolean
:rtype: None
"""
if not details:
if not signal_name or signal_name not in known_signals:
raise ValueError("Unknown signal name '%s'." % signal_name)
details_table = []
for proc in psutil.process_iter():
proc_pid = proc.pid
if not inclusive and proc_pid == os.getpid():
continue
proc_username = proc.username()
if username and username != proc_username:
continue
try:
proc_name = proc.name()
except:
proc_name = None
if name and name != proc_name:
continue
# add filtering for substrings to be searched in or matched with the command-line
try:
proc_cmdline = ' '.join(proc.cmdline())
except:
proc_cmdline = None
if proc_cmdline is None:
continue
else:
if cmdline_search and not re.search(cmdline_search, proc_cmdline):
continue
if cmdline_match and not re.match(cmdline_match, proc_cmdline):
continue
if list == True:
print(proc)
if details:
args = proc_pid, proc_username, proc_name, proc_cmdline
details_table.append(args)
continue
if interactive:
args = signal_name, proc_pid, repr(proc_username), repr(proc_name), repr(proc_cmdline)
do_it = raw_input('Sending %s to process (%s, %s, %s, %s)... (y/n)? ' % args).lower()
if do_it == 'y':
proc.send_signal(known_signals[signal_name])
if details_table:
headers = 'pid username pname pcommand'.split()
tab = tabulate.tabulate(details_table, headers=headers)
print(tab)
def list_available_signals():
"""
Print available symbolic signal names on stdout.
"""
sig_names = [k for k in known_signals.keys() if k.startswith('SIG')]
sig_names = ' '.join(sorted(sig_names))
print(sig_names)
def valid_signal_name(name):
"""
Is this a valid signal name?
"""
if name not in known_signals:
msg = "'%s' is an invalid signal." % name
msg += " Please use option --list to find valid signal names."
raise argparse.ArgumentTypeError(msg)
return name
if __name__ == '__main__':
desc = 'Send signals to processes selected by the command-line that created them.'
p = argparse.ArgumentParser(description=desc)
p.add_argument('-S', '--signal-name', metavar='SIGNAL_NAME',
type=valid_signal_name, default='SIGTERM',
help='Send a different signal instead of the default SIGTERM. The signal may be specified either as a name (with or without a leading SIG), or numerically.')
p.add_argument('-c', '--name', metavar='STRING',
help='Limit potentially matching processes to those matching the specified procname.')
p.add_argument('-u', '--username', metavar='STRING',
help='Limit potentially matching processes to those belonging to the specified user.')
p.add_argument('--cmdline_search', metavar='REGEXP',
help='Limit potentially matching processes to those with a command-line invocation containing the specified regex.')
p.add_argument('--cmdline_match', metavar='REGEXP',
help='Limit potentially matching processes to those with a command-line invocation matching the specified regex.')
p.add_argument('-l', '--list', action='store_true',
help='List the names of the available signals and exit, like in kill(1).')
p.add_argument('-d', '--details', action='store_true',
help='Print detailed information about the processes matched, but do not send any signal.')
p.add_argument('-i', '--interactive', action='store_true',
help='Ask interactively for each process before sending it the signal.')
p.add_argument('-I', '--inclusive', action='store_true',
help='Include this process running this tool.')
args = p.parse_args()
if args.list:
list_available_signals()
sys.exit(0)
signal_process(signal_name=args.signal_name, name=args.name, username=args.username,
cmdline_search=args.cmdline_search, cmdline_match=args.cmdline_match,
details=args.details, interactive=args.interactive,
inclusive=args.inclusive)
@deeplook
Copy link
Author

pkill -f is a viable alternative...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment