hpr4627 :: UNIX Curio #5 - Faster, Pussycat! Kill! Kill!
Sending signals to processes, or just checking if they exist
Hosted by Vance on Tuesday, 2026-04-28 is flagged as Clean and is released under a CC-BY-SA license.
unix curio, unix, signals, kill, ps.
(Be the first).
Listen in ogg,
opus,
or mp3 format. Play now:
Duration: 00:14:35
Download the transcription and
subtitles.
general.
This series is dedicated to exploring little-known—and occasionally useful—trinkets lurking in the dusty corners of UNIX-like operating systems.
Let me start by admitting that I've never actually seen the film referenced in the episode title, but I couldn't resist using it anyway.
If you've used the UNIX command line to any extent, chances are good that you are familiar with the
kill
command. A common use is to terminate a misbehaving program. But there is more behind how
kill
works, including a curio you might not know about.
The
kill
utility works by sending a "signal" to the targeted process. This signal is selected from a pre-defined list, and triggers the process to interrupt its normal flow and handle the signal before potentially returning back to its work. This "signal handler" can do whatever activities are written in its code, but typically it will take actions connected to the purpose of the specific signal received.
One option is for the process not to have a signal handler at all; in that case, there is a default action that the operating system will take on behalf of the process, depending on what the signal is. The possible default actions are to terminate the process, take some implementation-defined action (usually writing a core file to disk) and then terminate the process, stop (pause) execution of the process, continue execution of a stopped process, or ignore the signal.
By default,
kill
sends the TERM signal to the process, an indicator that it should terminate. Each signal has a name and a number assigned to it; SIGTERM is the name of the "terminate" signal. You can use the
-s
option with the name to choose which signal to send. The 'kill' command is specified to take these names without the SIG prefix, though some implementations will accept them either way. Also,
kill
is supposed to be case-insensitive when it comes to these names, but the convention is to write them in all upper case. The assigned numbers for signals can vary depending on the operating system, and on Linux, depending on what processor architecture you're on. However, there is a
short list of signals
1
that have a stable number assigned to them. Despite this, I recommend using the signal name in your scripts to make them clearer and to ensure maximum portability to different systems.
Well-behaved programs will have a signal handler that responds to the TERM signal by stopping what they are doing, cleaning up any open resources like temporary files, and promptly exiting. However, not every program behaves well, so sometimes it becomes necessary to send them the KILL signal. This one is special and cannot be handled or ignored by the program 2 ; the operating system will immediately terminate the program, possibly leaving a mess behind.
Two other signals that can come in handy sometimes are STOP and CONT. As you might expect, STOP forces a process to pause in the middle of whatever it was doing. Its counterpart, CONT (short for "continue"), causes it to resume execution. This can be useful if a program consumes CPU time when not actually doing anything worthwhile—sending it the STOP signal will end that, and when you're ready to use it again, CONT will cause it to pick up right where it left off. Like the KILL signal, STOP cannot be handled or ignored by the program. I have used this to pause the game FreeCiv when I wanted to break away to do something else, but didn't want to have to deal with exiting my current game and having to reload it later. Take note, though, that the program might get confused if it expects the system clock not to suddenly jump forward, as that is exactly how the situation will appear to it. Network connections or other resources the process is using that change while it is stopped are other potential trouble spots. Also be aware that a stopped graphical program will not update its window, so I find it best to minimize the window before stopping it and then continuing the process before trying to raise the window again.
Programs are not necessarily required to interpret signals in the way they are described. For example, the HUP signal was originally intended to be sent when a modem or serial connection hang-up occurred. Today, some daemons use it for other purposes and take a specific action in response. For example, the Apache web server will restart 3 , and NetworkManager will reload its configuration 4 . These uses of signals are usually described in the daemon's manual page, often in a separate section dedicated to signals.
While all this background might be interesting (or maybe not), it's pretty commonly known, so isn't really a curio. Our UNIX Curio for today is the "0" signal. This is actually not a signal at all; instead, it tells the
kill
utility to just check for the existence of a process. If the process exists,
kill
will exit with a status of 0. If it doesn't exist, the exit status will be greater than 0. This provides a handy way to check whether a particular process is still around. A shell command can use this exit status with its control structures like
if
to take a particular action depending on whether a particular process exists. Somewhat oddly, "0" is both the number
and
the name of this pseudo-signal.
Why would you want to do this? I have used it for a script to analyze log files that runs daily on a web server. Depending on how much traffic the site is getting, the log files can grow to the point where it takes longer than a day for the script to get through them. If a second instance of the script is started while one is still running, it will slow down both and if more keep being added, eventually the machine will run out of memory.
My solution was to create a .pid file containing the process ID number of the running script. You might see examples of these if you look in /run or /var/run on your system. The script creates a file named something like "myscript.pid" in this directory containing its own process ID, which can be accessed in the shell with the variable
$$
. When my script starts, it checks to see whether this file exists. If so, it uses
kill -s 0 $(cat /run/myscript.pid)
to see if the previous process still exists. If the process is no longer around, that's a sign that it exited abnormally before it had the chance to delete the .pid file, so the script removes the abandoned .pid file, replaces it with a new one containing the current process ID, and continues with its work. If the previous process is still around, my script exits with a message to that effect. This way, I can be sure that only one instance of the script will ever be running at one time.
Be aware that the
kill
utility might also return a non-zero exit status if the user running it does not have privileges to send a signal to the process with the specified ID. This is not a concern if you are running a script as the root user, but could be if you are not. This can occur even if you aren't actually sending a signal, just using the "0" pseudo-signal to check if a process exists.
There is a weakness in this method. UNIX-like systems generally have a limit to the quantity of process ID numbers that can be issued, so they are reused over time. (However, there will never be two processes with the same ID number running at the same time.) Typically, the first process that is run on start-up will be given the ID number 1, and each subsequent process will get the next higher number. Once the maximum is reached, the system starts again at the beginning with the lowest number not in use. It is possible for the script to crash and leave behind the .pid file, then the same process ID could be recycled and actively used for another program, causing a new instance of the script to give up. The chances of this are small enough that for my purposes, it's not worth worrying about. But you should be aware that it could happen.
I should also note that it's not strictly necessary to use
kill
for the purpose I described. The
ps
utility can also be given a process ID with the
-p
option; if the process exists, the exit status will be 0, otherwise it will be greater than 0. In this case, you could also use the output to check that the name of the command matches what you expect, helping avoid the problem of a recycled process ID. In addition,
ps
doesn't concern itself with permissions for sending signals, so it will report on the existence of a process no matter what user you are running it as. From an efficiency standpoint,
kill
generally requires fewer resources to run (in fact, it is built in to some shells), but functionally
ps
can also do the job.
So keep in mind that
kill
is capable of doing more than just killing off programs—maybe you can put it to one of these uses for your needs.
References:
- Kill specification https://pubs.opengroup.org/onlinepubs/009695399/utilities/kill.html
- signal.h specification https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
- Stopping and Restarting Apache HTTP Server https://httpd.apache.org/docs/2.4/stopping.html#hup
- NetworkManager: Signals https://networkmanager.dev/docs/api/latest/NetworkManager.html#id-1.2.2.9
Appendix 1 - example script:
#!/bin/sh
# Use your own unique name here - be sure you can write to this location
pidfile="/var/run/myscript.pid"
# Exit if previous run hasn't completed yet
if [ -f "$pidfile" ] ; then
oldpid=$(cat "$pidfile")
if kill -s 0 $oldpid ; then
echo "${0}: Not running script, older process $oldpid still active"
exit 1
else
echo "${0}: Removing old pidfile from nonexistent process $oldpid"
rm -f "$pidfile"
fi
fi
# Create pidfile
echo $$ > "$pidfile"
## Insert commands to do the actual work of the script here
# Remove pidfile
rm -f "$pidfile"
Appendix 2
- another version of the script using
ps
instead of
kill
, checking that an existing process ID is actually the same command, and with extra validation of the contents of the pidfile; perhaps better for use by a non-root user:
#!/bin/sh
# Use your own unique name here - be sure you can write to this location
pidfile="$HOME/myscript.pid"
# Exit if previous run hasn't completed yet
if [ -f "$pidfile" ] ; then
oldpid=$(( 1 * $(cat "$pidfile") ))
if [ -n "$oldpid" ] && [ "$oldpid" -gt 1 ] ; then
:
else
echo "${0}: Not running script, $pidfile contents invalid"
exit 1
fi
# Test if old process ID exists
if oldcmd="$( ps -o comm= -p $oldpid )" &&
# Also test if command name of old process is same as current script
[ "$oldcmd" = "${0##*/}" ] ; then
echo "${0}: Not running script, older process $oldpid still active"
exit 1
else
echo "${0}: Removing old pidfile from nonexistent process $oldpid"
rm -f "$pidfile"
fi
fi
# Create pidfile
echo $$ > "$pidfile"
## Insert commands to do the actual work of the script here
# Remove pidfile
rm -f "$pidfile"