67

Is there a way to map a signal number (e.g. signal.SIGINT) to its respective name (i.e. "SIGINT")?

I'd like to be able to print the name of a signal in the log when I receive it, however I cannot find a map from signal numbers to names in Python, i.e.:

import signal
def signal_handler(signum, frame):
    logging.debug("Received signal (%s)" % sig_names[signum])

signal.signal(signal.SIGINT, signal_handler)

For some dictionary sig_names, so when the process receives SIGINT it prints:

Received signal (SIGINT)
tshepang
  • 12,111
  • 21
  • 91
  • 136
Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343

8 Answers8

127

With the addition of the signal.Signals enum in Python 3.5 this is now as easy as:

>>> import signal
>>> signal.SIGINT.name
'SIGINT'
>>> signal.SIGINT.value
2
>>> signal.Signals(2).name
'SIGINT'
>>> signal.Signals['SIGINT'].value
2
pR0Ps
  • 2,752
  • 2
  • 23
  • 26
  • 5
    pylint-3.7 gives me this: `E1101: Module 'signal' has no 'Signals' member (no-member)`. Why does this happen? Doesn't pylint "see" the enum? – Paal Braathen Oct 03 '19 at 10:00
  • @Paal: It's `(signal_number)` ,e.g.: `signal.Signals(2).name`not square brackets. – cfi Feb 03 '22 at 09:37
  • @cfi I'm not sure I follow. I found out that my comment was related to a pylint issue https://github.com/PyCQA/astroid/pull/1172 – Paal Braathen Feb 05 '22 at 14:35
  • @Paal: Never mind. In my case I got the exact same pylint message because I mistyped the `.name` attribute access using `signal.Signals[2].name` when it should rather be `signal.Signals(2).name`. For me there was no pylint issue, but a user issue. My previous comment lacks context. I do apologize. – cfi Feb 14 '22 at 13:54
34

There is none, but if you don't mind a little hack, you can generate it like this:

import signal
dict((k, v) for v, k in reversed(sorted(signal.__dict__.items()))
     if v.startswith('SIG') and not v.startswith('SIG_'))
Rufflewind
  • 8,545
  • 2
  • 35
  • 55
Wolph
  • 78,177
  • 11
  • 137
  • 148
  • Strictly speaking this will map 1 to `SIG_IGN` and `SIGHUP` on most platforms, so I suppose the test should be if `v.startswith('SIG') and not v.startswith('SIG_')`. – Brian M. Hunt Mar 31 '10 at 12:45
  • It also double-maps 6 to `SIGABRT` and `SIGIOT` on Mac OS X (though they could be used interchangeably, I suppose, unlike `SIG_IGN` - which isn't a signal). – Brian M. Hunt Mar 31 '10 at 12:48
  • @Brian: very true... it's not a perfect solution by far. But it's atleast somewhat platform independent. – Wolph Mar 31 '10 at 21:15
  • No need to sort. The dict will store as it sees fit anyway. – tbc0 Mar 24 '15 at 20:03
  • 3
    @tbc0: the sort is to give a consistent list of signals since there are duplicates in the list, so it's most certainly needed – Wolph Mar 24 '15 at 20:05
  • @Wolph not strictly needed, but I concede your point about consistency. – tbc0 Mar 24 '15 at 20:23
  • 1
    @JohnGreene I've just tested the code in both Python 3.8 and Python 3.9 and it works without an issue here. What isn't working for you? – Wolph Oct 19 '20 at 07:50
  • Down-voting this, not because the answer is bad at the time it was written, but it is now very out of date. All supported versions of python support [using the Signals enum](https://stackoverflow.com/a/35996948/453851). – Philip Couling Aug 03 '21 at 12:00
12

As of Python 3.8, you can now use the signal.strsignal() method to return a textual description of the signal:

>>> signal.strsignal(signal.SIGTERM)
'Terminated'

>>> signal.strsignal(signal.SIGKILL)
'Killed'
PiranhaPhish
  • 175
  • 1
  • 9
12

The Python Standard Library By Example shows this function in the chapter on signals:

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

You can then use it like this:

print "Terminated by signal %s" % SIGNALS_TO_NAMES_DICT[signal_number]
devin_s
  • 3,345
  • 1
  • 27
  • 32
  • 2
    or (perhaps slightly more preferable) `SIGNALS_TO_NAMES_DICT.get(signal_number, "Unnamed signal: %d" % signal_number)` – Brian M. Hunt Dec 15 '11 at 23:45
  • As @Wolph points out in his answer, this solution doesn't guarantee a consistent name to duplicate signals. – tbc0 Mar 24 '15 at 20:24
3

I found this article when I was in the same situation and figured the handler is only handling one signal at a time, so I don't even need a whole dictionary, just the name of one signal:

sig_name = tuple((v) for v, k in signal.__dict__.iteritems() if k == signum)[0]

there's probably a notation that doesn't need the tuple(...)[0] bit, but I can't seem to figure it out.

ssc
  • 9,528
  • 10
  • 64
  • 94
  • signame = [v for v, k in signal.__dict__.iteritems() if k == signum][0] works fine. – dwelch91 Feb 15 '13 at 21:21
  • 2
    "signame = next(v for v, k in signal.__dict__.iteritems() if k == signum)" stops when it finds it instead of continuing through the rest of the dict (also, it doesn't have the [0] that @ssc didn't like :D). – mikenerone Oct 30 '13 at 10:18
  • If v, k are supposed to stand for keys and values, then the names should be reversed, e.g. sig_name = tuple((k) for k, v in signal.__dict__.iteritems() if v == signum)[0] But then there are other values in signal's dict whose value may match v, in which case, depending on the order iteritems() returns items, you may print a nonsense name. – tbc0 Mar 24 '15 at 19:21
1

Well, help(signal) says at the bottom:

DATA
    NSIG = 23
    SIGABRT = 22
    SIGBREAK = 21
    SIGFPE = 8
    SIGILL = 4
    SIGINT = 2
    SIGSEGV = 11
    SIGTERM = 15
    SIG_DFL = 0
    SIG_IGN = 1

So this should work:

sig_names = {23:"NSIG", 22:"SIGABRT", 21:"SIGBREAK", 8:"SIGFPE", 4:"SIGILL",
             2:"SIGINT", 11:"SIGSEGV", 15:"SIGTERM", 0:"SIG_DFL", 1:"SIG_IGN"}
Daniel G
  • 67,224
  • 7
  • 42
  • 42
  • 4
    Unfortunately different platforms have different signal numbers, so this isn't portable. Thanks, though. – Brian M. Hunt Mar 31 '10 at 12:41
  • 1
    For example, here's the Mac OS X `help(signal)`: SIGABRT = 6 SIGALRM = 14 SIGBUS = 10 SIGCHLD = 20 SIGCONT = 19 SIGEMT = 7 SIGFPE = 8 SIGHUP = 1 SIGILL = 4 SIGINFO = 29 SIGINT = 2 SIGIO = 23 SIGIOT = 6 SIGKILL = 9 SIGPIPE = 13 SIGPROF = 27 SIGQUIT = 3 SIGSEGV = 11 SIGSTOP = 17 SIGSYS = 12 SIGTERM = 15 SIGTRAP = 5 SIGTSTP = 18 SIGTTIN = 21 SIGTTOU = 22 SIGURG = 16 SIGUSR1 = 30 SIGUSR2 = 31 SIGVTALRM = 26 SIGWINCH = 28 SIGXCPU = 24 SIGXFSZ = 25 – Brian M. Hunt Mar 31 '10 at 12:44
  • 1
    Ah - I really should have realized that, all things considered, especially since I've experienced how much different signals are on Windows and Linux. WoLpH's solution is cleaner anyway :) – Daniel G Mar 31 '10 at 16:06
  • 1
    I wouldn't say my solution is cleaner. Yes, it works on every platform but if your solution is an option, than I'd definately use it and use my version as a fallback. – Wolph Mar 31 '10 at 23:58
0

Building on another answer:

import signal

if hasattr(signal, "Signals"):
    def _signal_name(signum):
        try:
            return signal.Signals(signum).name
        except ValueError:
            pass
else:
    def _signal_name(signum):
        for n, v in sorted(signal.__dict__.items()):
            if v != signum:
                continue
            if n.startswith("SIG") and not n.startswith("SIG_"):
                return n

def signal_name(signum):
    if signal.SIGRTMIN <= signum <= signal.SIGRTMAX:
        return "SIGRTMIN+{}".format(signum - signal.SIGRTMIN)
    x = _signal_name(signum)
    if x is None:
        # raise ValueError for invalid signals
        signal.getsignal(signum)
        x = "<signal {}>".format(signum)
    return x
Community
  • 1
  • 1
NULL
  • 2,549
  • 1
  • 15
  • 17
0

for signal_value of positive number (signal number), or negative value (return status from subprocess):

import signal

signal_name = {
        getattr(signal, _signame): _signame
        for _signame in dir(signal)
        if _signame.startswith('SIG')
    }.get(abs(signal_value), 'Unknown')