Sensible asynchronous job notification in bash(1)

This page contains a patch to bash, which introduces a third mode of job control notification. Written by Simon Tatham.

Explanation

As shipped by default, bash has two modes of job control notification:

set +b (the default mode)
In this mode, every time bash is about to print its prompt, it first checks to see whether any background jobs have stopped or terminated, and if so it prints notification messages before displaying the prompt.
set -b
In this mode, when any background job stops or terminates, bash receives a SIGCHLD signal, and its signal handler displays the notification message immediately - even if bash is currently in the middle of waiting for a foreground process to complete.

Neither of these modes is really satisfactory. set -b has no awareness of the state of the terminal, so its notifications are likely to corrupt the display of any full-screen application you might be running (such as a text editor or MUA). In particular, this mode cannot even interact sensibly with bash's own command line editing! Try running ‘sleep 1 &’ in this mode, and then immediately beginning to type a command line. You will find that when the notification is printed, you are left with the display in a somewhat non-obvious state.

By contrast, set +b never corrupts the display, but it has its own problems. In particular, suppose you are testing a GUI program whose source code you're editing in a separate editor window. (Let's assume it's written in an interpreted language.) You might have a terminal window in which you keep typing the same command, ‘myprogram &’. Every time you make changes to the program and want to restart it, you close down the current instance of it, and repeat this command in your shell window. Now bash will launch the new instance of the program before printing the notification that the old one has finished - and because printing that notification is the moment at which the job number allocation gets reset, this also means that your job numbers will increase without bound unless you deliberately hit Return at a point when no background job is running. Eventually one instance of your program will hang and you'll have to type ‘kill %134’, instead of the ‘kill %1’ you would prefer.

What's needed is a third mode.

If you press ^X ^V at a normally configured bash prompt, the shell will move to a new line on the terminal, display its version information, and then redisplay the command line you were editing beforehand, so you don't lose track of what you were doing. This is how job terminations should be displayed.

The patch I provide below implements this third mode. Like the default set +b mode, bash will never interrupt a running foreground application to tell you about a job termination - so you don't need to worry that full-screen applications will have their displays corrupted. But while actually sitting at the shell command prompt, job notifications are displayed immediately rather than waiting for you to press Return; and when a notification is shown, your current command line is redisplayed immediately afterwards so that you don't lose track of whatever command-line editing you were doing.

Syntax

This patch adds a new setting to bash's set command, called notify-sensibly. You enable this feature using set -o notify-sensibly, and disable it again using set +o notify-sensibly.

The notify-sensibly feature has the effect of changing the set -b mode from the normal one into my new one. So to enable my new semi-asynchronous mode, you have to do both set -o notify-sensibly and set -b. (Yes, this is ugly; but the set framework doesn't make it syntactically easy to implement a three-way switch.)

I think set +b is usually preferable to set -b in the absence of my feature. Therefore, if you use the same .bashrc on more than one system, I recommend the following code fragment to ensure you get optimal behaviour on versions of bash both with and without my patch:

set -o notify-sensibly 2>/dev/null && set -b

(On an unpatched bash, the attempt to enable notify-sensibly will print an error message - here redirected to /dev/null - and return failure, so that the subsequent set -b will not be executed. On a patched bash, both commands will complete silently and successfully and my third mode will be enabled.)

The patch

Here are patches to several versions of bash. (The various Linux systems I use do not all run the same distribution or version of bash, so I prepared patches against several different versions.)

In addition, here are Debian binary packages (for i386) of the patched bash. These should install cleanly on any system running Debian 5.0 (‘lenny’), 4.0 (‘etch’), 3.1 (‘sarge’) or 3.0 (‘woody’).

Internals

In case it's useful to anybody, here's a brief overview of what I did.

The normal set -b mode works by actually displaying the notifications in the SIGCHLD handler. In my first attempt to code this patch (around 1997), I initially tried to imitate this structure. However, it proved difficult to safely determine whether an instance of readline was in progress or not - setting a flag just before calling readline left a race condition whereby, if a job terminated at just the wrong moment, rl_redisplay() would be called before readline was initialised. It worked well enough in practice, and I ran it on my own machine in full knowledge of the risks, but it wasn't good enough to distribute more widely.

When I came back to the problem in 2003, I set up an internal pipe within the bash process. The SIGCHLD handler simply writes a byte to this pipe; and instead of calling readline() to read an entire command line, I now use the rl_callback_handler_install() interface to the Readline library. This allows me to do my own select() between the input file descriptor and the internal pipe used to indicate SIGCHLD. So if a job terminates in the middle of command-line editing, the signal handler writes to the pipe, which immediately terminates the select(), and my code can print the job notification, call rl_redisplay() in the knowledge that it's safe to do so, and go back to editing. Whereas if a job terminates while bash is doing anything else, the byte written to the signal pipe will simply sit there until bash is about to print its prompt, at which point it will do the usual set +b check for job terminations and also empty the pipe buffer.

Status

I wrote this patch in July 2003. It spent about a year in alpha testing, and then in July 2004 I published it on the web for other people to beta-test. I mailed the bash maintainers about it at that time as well, but they never replied. (I certainly wouldn't blame them if they thought it was far too hacky!)

Therefore I'm resigned to maintaining this patch as a third-party addition to bash, and I expect to adapt it to new releases of bash as and when I find myself dealing with those releases.


(comments to anakin@pobox.com)
(thanks to chiark for hosting this page)
(last modified on Tue Apr 7 22:55:05 2009)