Pages: Welcome | Projects

kill tail(1) when sh exits

2019/10/17
Tags: [ shell ] [ Today I learned ]

Let's consider the case of a shell script that wants to keep an eye on a log file. The log file gets populated with useful information during the execution of the script itself.

tail -F /var/log/x.log &
generate_logs --output=/var/log/x.log

This obviously works, but the shell termination won't kill the tail process, so the script execution will leak resources.

As a solution, the POSIX shell provides a built-in named trap, documented here. In short it allows to define actions to be executed upon signaling, and that includes shell termination. It is similar to atexit(3) in POSIX C.

The previous script becomes then something like this:

tail -F /var/log/x.log &
tailpid="$!"
atexit() {
    kill -term "$tailpid"
}
trap atexit EXIT
generate_logs --output=/var/log/x.log

This will terminate the tail process when the shell exits. Note that it will not work when the shell is interrupted (e.g. SIGINT), but it should be enough to associate the trap with a couple of additional signals.

Oh, and I just hope that tail does not die before the shell terminates, because the shell won't forget tail's pid. The shell will try to kill tail even if it is no longer alive. Will the same pid be taken by another process?

What I found out today is that if you are running GNU tail, you can simply rely on the --pid option:

tail -F /var/log/x.log --pid="$$" &
generate_logs --output=/var/log/x.log

Or even (if that fits your needs):

generate_logs --output=/var/log/x.log &
tail -F /var/log/x.log --pid="$!"

GNU tail will just keep an eye on the specified pid, and terminate together with the corresponding process. This is very elegant, but unfortunately not portable.

EDIT

I forgot to mention (and thanks kn for reminding me of it) that kill system call accepts 0 as a pseudo process identifier, which allows to kill all processes having the same process group. The kill command allows for the same semantics, so we have also this possibility:

tail -F /var/log/x.log &
trap 'kill -term 0' EXIT
generate_logs --output=/var/log/x.log

It is less specific, as every process having the same process group will be signaled, but it is also good enough in most cases, and way simpler :)

NOTES

Comments on lobsters.

Thanks to kn on Lobsters: he spotted a typo (I wrote $? instead of $! in my introduction) and mentioned kill 0.