Last active
April 20, 2016 13:27
-
-
Save deeplook/5e087bf392ec6b538df60ced46185f93 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
pkill -f
is a viable alternative...