35

In python 2.6 under Linux, I can use the following to handle a TERM signal:

import signal
def handleSigTERM():
    shutdown()
signal.signal(signal.SIGTERM, handleSigTERM)    

Is there any way to setup a handler for all signals received by the process, other than just setting them up one-at-a-time?

Justin Ethier
  • 131,333
  • 52
  • 229
  • 284

8 Answers8

45

As of Python 3.5, the signal constants are defined as an enum, enabling a nicer approach:

import signal

catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
for sig in catchable_sigs:
    signal.signal(sig, print)  # Substitute handler of choice for `print`
doctaphred
  • 2,504
  • 1
  • 23
  • 26
  • Could the downvoter please leave a comment? Is there something wrong with this answer? – doctaphred Apr 23 '16 at 11:15
  • This is actually quite useful. Thanks! Can you please confirm whether this works on windows? – Dev Aggarwal Nov 03 '18 at 20:30
  • 1
    I don't know how signals work on Windows, but it looks like the core functionality is available: https://docs.python.org/3/library/signal.html#signal.signal Note that even though the signal function only accepts certain values, it looks like the Signals enum is constructed with the appropriate signals for the platform: https://github.com/python/cpython/blob/master/Lib/signal.py#L8-L13 So I would guess this approach would work, possibly with some minor tweaks. But again, I'm not sure how signals work or even what they mean on Windows, so it might not work at all. – doctaphred Nov 16 '18 at 20:16
39

You could just loop through the signals in the signal module and set them up.

for i in [x for x in dir(signal) if x.startswith("SIG")]:
  try:
    signum = getattr(signal,i)
    signal.signal(signum,sighandler)
  except (OSError, RuntimeError) as m: #OSError for Python3, RuntimeError for 2
    print ("Skipping {}".format(i))
Noufal Ibrahim
  • 71,383
  • 13
  • 135
  • 169
  • 5
    That should be RuntimeError, not RunTimeError. couldn't edit, as just one character change. – tobych Sep 01 '11 at 04:14
  • 2
    Your list comprehension will include `SIG_IGN`, which is an action not a signal. Since this actions 'value' corresponds to `1`, which is the value for `SIGHUP`, you will be setting this signal twice. For this specific case that doesn't matter, however this code may break down for other similar purposes. – Bryce Guinta May 16 '17 at 10:14
  • 1
    This answer does not apply to Python 3 since trying to set unblockable signals such as `SIGKILL` for example throw an `OSError` now instead of a `RuntimeError`. – Bryce Guinta May 16 '17 at 10:36
  • @BryceGuinta Fixed. This should work for Pythons 2 and 3 now. – Noufal Ibrahim May 16 '17 at 17:16
  • You will get `ValueError` for `SIG_BLOCK` and `SIG_DFL` in python 3.5 – yukashima huksay Jan 14 '18 at 07:41
11

If you want to get rid of the try, just ignore signals that cannot be caught.

#!/usr/bin/env python
# https://stackoverflow.com/questions/2148888/python-trap-all-signals
import os
import sys
import time
import signal

SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) \
    for n in dir(signal) if n.startswith('SIG') and '_' not in n )


def receive_signal(signum, stack):
    if signum in [1,2,3,15]:
        print 'Caught signal %s (%s), exiting.' % (SIGNALS_TO_NAMES_DICT[signum], str(signum))
        sys.exit()
    else:
        print 'Caught signal %s (%s), ignoring.' % (SIGNALS_TO_NAMES_DICT[signum], str(signum))

def main():
    uncatchable = ['SIG_DFL','SIGSTOP','SIGKILL']
    for i in [x for x in dir(signal) if x.startswith("SIG")]:
        if not i in uncatchable:
            signum = getattr(signal,i)
            signal.signal(signum,receive_signal)
    print('My PID: %s' % os.getpid())
    while True:
        time.sleep(1)
main()
guettli
  • 25,042
  • 81
  • 346
  • 663
eric poelke
  • 119
  • 1
  • 3
  • The code is not properly indented but I cannot edit it because SO don't let me make a change that small. All the for should be indented with one level of indentation less. – krenel May 10 '13 at 15:04
2

Works on Windows 10 and Python 3.7:

import signal
import time

def sighandler(signal,frame):
    print("signal",sig,frame)
    return

catchable_sigs = set(signal.Signals)
for sig in catchable_sigs:
    try:
        signal.signal(sig, sighandler)
        print("Setting ",sig)
        print ("value {}".format(sig))
    except (ValueError, OSError, RuntimeError) as m:
        print("Skipping ",sig)
        print ("Value {}".format(sig))


# press some keys or issue kill
x = 0
while x < 5:
    time.sleep(4)
    x += 1

Results:

Skipping  Signals.CTRL_C_EVENT
Value 0
Skipping  Signals.CTRL_BREAK_EVENT
Value 1
Setting  Signals.SIGINT
value 2
Setting  Signals.SIGILL
value 4
Setting  Signals.SIGFPE
value 8
Setting  Signals.SIGSEGV
value 11
Setting  Signals.SIGTERM
value 15
Setting  Signals.SIGBREAK
value 21
Setting  Signals.SIGABRT
value 22
PeterB
  • 156
  • 1
  • 5
1

Here's a 2/3 compatible way which doesn't have as many pitfalls as the others:

from itertools import count
import signal

def set_all_signal_signals(handler):
    """Set all signals to a particular handler."""
    for signalnum in count(1):
        try:
            signal.signal(signalnum, handler)
            print("set {}".format(signalnum))
        except (OSError, RuntimeError):
            # Invalid argument such as signals that can't be blocked
            pass
        except ValueError:
            # Signal out of range
            break

Since signalnum is just a number, iterate over 1 to out of range setting the signal to a particular handle.

Bryce Guinta
  • 3,456
  • 1
  • 35
  • 36
1

In Python3.8 we've got a new function signal.valid_signals() https://docs.python.org/3/library/signal.html#signal.valid_signals

import signal
for sig in signal.valid_signals():
    print(f"{sig:2d}",sig)
Mortin
  • 13
  • 3
-1

For Python 3:

for sig in signal.Signals:
    try:
        signal.signal(sig, sighandler)
    except OSError:
        print('Skipping', sig)
Quanlong
  • 24,028
  • 16
  • 69
  • 79
-2

That code won't work in the current version of python. There are many variables starting with SIG with the same value. For instance, SIGHUP and SIG_UNBLOCK are both 1. The only way I could think of to get a list of actual signals was to just make it myself.

from signal import *    
signals = {
        SIGABRT: 'SIGABRT',
        SIGALRM: 'SIGALRM',
        SIGBUS: 'SIGBUS',
        SIGCHLD: 'SIGCHLD',
        SIGCONT: 'SIGCONT',
        SIGFPE: 'SIGFPE',
        SIGHUP: 'SIGHUP',
        SIGILL: 'SIGILL',
        SIGINT: 'SIGINT',
        SIGPIPE: 'SIGPIPE',
        SIGPOLL: 'SIGPOLL',
        SIGPROF: 'SIGPROF',
        SIGQUIT: 'SIGQUIT',
        SIGSEGV: 'SIGSEGV',
        SIGSYS: 'SIGSYS',
        SIGTERM: 'SIGTERM',
        SIGTRAP: 'SIGTRAP',
        SIGTSTP: 'SIGTSTP',
        SIGTTIN: 'SIGTTIN',
        SIGTTOU: 'SIGTTOU',
        SIGURG: 'SIGURG',
        SIGUSR1: 'SIGUSR1',
        SIGUSR2: 'SIGUSR2',
        SIGVTALRM: 'SIGVTALRM',
        SIGXCPU: 'SIGXCPU',
        SIGXFSZ: 'SIGXFSZ',
        }

for num in signals:
    signal(num, h)
fixxxer
  • 15,568
  • 15
  • 58
  • 76
enigmaticPhysicist
  • 1,518
  • 16
  • 21
  • It's not necessary. All the signals still start with `SIG` but you need to add a double check to make sure that you ignore the `SIG_` ones. – Noufal Ibrahim May 26 '17 at 11:25