In a bash shell in a terminal emulator window of lxterminal, I run

$ trap "echo hello" SIGHUP 
$ kill -s HUP $$

and then I close the terminal emulator window.

Does closing the terminal emulator window only cause SIGHUP sent to the controlling process i.e. the bash process?

Since the SIGHUP trap doesn't terminate the bash process, I expect the bash process isn't terminated, but why is the bash process actually terminated?

Same thing happen if I change the trap to "" (ignore).

Terminal emulator matters. In bash running in a xterm window, setting trap to "" will make a xterm window not closable, while setting trap to echo hello can still close a xterm window.



3 Answers 3


[ I'll ignore the actual and possible behavior of different terminal emulators; a completely reasonable behavior would be to send a ^D (VEOF) to the pty on a window close / WM_DELETE_WINDOW, instead of tearing it down and causing the process running in it to receive a SIGHUP; the following assumes xterm, which will send a SIGHUP to the shell's process group in that case ].

The behavior you're seeing is because of the readline library, which is installing its own signal handlers. If you try the following:

xterm -e bash --noediting

(or dash, zsh or ksh instead of bash --noediting), then run

trap 'echo HUP' HUP

in the terminal, the window will become unclose-able; the shell will just print HUP as expected on trying to close the window; forcefully closing it (eg. with xkill) will cause the shell to exit with EIO errors, which is completely expected, since the pty was torn down.

Here is a simpler testcase for the behavior you're observing, not involving terminal emulators. Run the following in your terminal:

bash --rcfile <(echo 'trap "echo HUP" HUP')

Then kill -HUP $$ will just print HUP, but (sleep 1; kill -HUP $$) & (or kill -HUP <pid> from another window) will cause the shell to print exit and exit, unless you started it with --noediting (= don't use readline)

The readline() function as called by bash will install its own signal handlers upon waiting for input from the user, and restore the original handlers upon returning; a SIGHUP while waiting for input from the user will cause it to return NULL, which will be treated as EOF by bash (in the yy_readline_get() function), before having the chance to run the deferred trap handler.

  • Thanks. Is readline library a shared library? Does bash dynamically linked to it or dynamically load it by dlopen()?
    – Tim
    Jan 2, 2019 at 2:31
  • Is closing a terminal window an event which can generate SIGHUP? If that were true, how can setting up SIGHUP handler prevent the terminal window from closing, given that the terminal window is already closed?
    – Tim
    Jan 2, 2019 at 4:09
  • 1. readline may be a shared lib, but on my system bash is linked statically to its own version of it 2. that's what xterm does when the user tries to close the window -- it sends a SIGHUP itself to process group of the terminal, and it won't actually destroy the window and the pty until the process started in it exits of its own volition.
    – user313992
    Jan 2, 2019 at 6:18
  • other terminal emulators like mlterm or lxterminal will just close the pty, which will cause the kernel to send a SIGHUP; but even if the shell running in the pty ignores or handles that sig, it will die with EIO errors when trying to read from, write to or do termios ioctls on the slave part of a pty with no master.
    – user313992
    Jan 2, 2019 at 6:39

Bash also exits when it runs out of input to read. That can happen several ways, common ones being reading the last line of a shell script, the user typing control-D, or... closing the terminal window.

(You could also try bash -i < /dev/null and note how it immediately exits, because it ran out of input).

  • Thanks. When bash is waiting with a prompt, but there is never any input, how does it know it is not running out of input?
    – Tim
    Dec 30, 2018 at 18:02
  • Does "Bash also exits when it runs out of input to read" happen not by signal? (ctrl-D doesn't send a signal?)
    – Tim
    Dec 30, 2018 at 18:03
  • 2
    @Tim because the kernel treats the cases differently. If you read from a terminal/pipe/character device/etc. and there is no input available, your program just waits in the read syscall until either there is, or there is affirmatively not going to be any more. (There are a bunch of syscalls that handle reading/writing to files/devices, way beyond what can be covered in a comment, it's also possible to check the status without waiting). Typically this doesn't involve signals at all.
    – derobert
    Dec 30, 2018 at 18:09

There's a lot more happening under the covers than just a SIGHUP. For example, closing the terminal window also causes the pty to be closed, so any output or input will return I/O errors.

We can see this by running strace on the bash process when you close the window.

We start with the bash process waiting at the prompt (the pselect()) and then close the window...

% strace -p 1090
strace: Process 1090 attached
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=3409, si_uid=500} ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Interrupted system call)
ioctl(2, TCXONC, TCOON)                 = -1 EIO (Input/output error)
ioctl(0, TCGETS, 0x7ffe1d1734e0)        = -1 EIO (Input/output error)
ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = -1 EIO (Input/output error)

We're starting to see the I/O errors as bash tries to process the handler...

At this point notice that bash decided to shutdown because it has no control terminal, so it restores all the signal handlers and sends itself another SIGHUP

rt_sigaction(SIGINT, {sa_handler=0x467410, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGTERM, {sa_handler=0x466f10, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGHUP, {sa_handler=0x4640e0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGALRM, {sa_handler=0x4676d0, sa_mask=[HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGWINCH, {sa_handler=0x466f00, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4baaa0, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
getpid()                                = 1090
kill(1090, SIGHUP)                      = 0
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=1090, si_uid=500} ---

And then the close down process continues (rewriting .bash_history and so on).

So it's not the initial SIGHUP that terminates the shell, it's the loss of the pty providing a terminal for input.

  • Interestingly, here those ioctls succeed — probably a different terminal emulator (I use xterm). Even writing the "exit" word succeeds here (write(2, "exit\n", 5) = 5)
    – derobert
    Dec 30, 2018 at 18:06
  • Could be a timing issue, different version of bash, and so on, but the result is the same; eventually bash sees an error and gives up :-) Dec 30, 2018 at 18:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .