Bash – How does bash’s disown work

bashdisown

On a Linux system, the bash builtin command disown can be used to remove jobs from the current session. How does bash implement this feature? Is setsid() used here, and if so, how does bash trigger the child process to invoke setsid()? Does it use signal?

Best Answer

No. disown does not remove a job from the current session [1], does not detach it from the terminal, and does not affect which signals the kernel will send it when the session leader exits or when the controlling terminal is torn down.

disown only works on bash's own job table, only changes bash's idea of which jobs it's in control of, and only affects bash's own behavior, namely which jobs it will resend a SIGHUP received by the bash process. That SIGHUP resending is an extra feature of bash [2], not required by the standard and not related to the job control provided by the OS.

You can see it with a simple example, where I'm using script(1) to create a pty and an interactive shell session running in it:

$ script /dev/null -qc bash
$ sh -c 'sleep 555 & sleep .1; kill -STOP $!; trap "echo hupped!" HUP; sleep 666' &
[1] 3837
$ disown -a
$ jobs
   # no jobs known to bash
$ pgrep -as0
   # show all processes from the current session
3836 bash
3837 sh -c sleep 555 & sleep .1; kill -STOP $!; trap "echo hupped!" HUP; sleep 666
3838 sleep 555
3841 sleep 666
$ kill -HUP $$
   # seppuku the session leader
Hangup
hupped!

Here the kernel sends a SIGHUP signal to the background process group (= job) because one of its processes is stopped, and disowning it will not prevent that from happening.

All processes from the sh -c '...' are part of the same job, including the "background" sleep &; shell scripts don't do job control by default.

If no members of the background process group are stopped, no SIGHUP is sent:

$ script /dev/null -qc bash
$ sh -c 'sleep 555 & trap "echo hupped!" HUP; sleep 666' &
[1] 3270
$ disown -a
$ kill -HUP $$
    # sleep 555, 666 and sh -c are still running

Finally, bash will send a SIGHUP to all the jobs from its table (only those started by itself and not disowned), no matter if bash is the session leader or not, or if the jobs are running, stopped, etc:

$ bash
$ sh -c 'sleep 555 & trap "echo hupped!" HUP; sleep 666' &
[1] 3413
$ kill -HUP $$
Hangup
hupped!
Hangup

[1] which would be impossible to do anyway; setsid() is only able to make into a new session a process which is not a process group leader, there is no way to move a whole job into a new or existing session.

[2] which is documented in the bash's manpage:

The shell exits by default upon receipt of a SIGHUP. Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped. Stopped jobs are sent SIGCONT to ensure that they receive the SIGHUP. To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with the disown builtin (see SHELL BUILTIN COMMANDS below) or marked to not receive SIGHUP using disown -h.

There's is also shopt -s huponexit which cause a login bash shell to send a HUP to its jobs upon exiting (whether because of a signal or not), which again overlaps in confusing ways with the standard job control features of the OS.