Description: When jobs are stopped it normally takes two attempts to exit with no intervening commands. However, if there is exactly one intervening command, ^D exits immediately instead of printing a warning. (The "exit" builtin works correctly on the other hand.)
Repeat-By: $ cat ^Z [1]+ Stopped cat $ ^D exit There are stopped jobs. $ echo intervening command intervening command $ ^D exit <terminates> # expected behaviour: print "There are stopped jobs" instead of exiting Cause: The exit_or_logout function in builtins/exit.def uses last_shell_builtin to check if the last command was also an exit attempt. However, ^D bypasses the command parser so last_shell_builtin will actually be the second to last command. In the example above, when the second ^D is typed, this_shell_builtin is the "echo" command and last_shell_builtin is "exit" from the first ^D. So exit_or_logout thinks this is the second exit in a row when it isn't. Fix: Patch attached. It makes exit_or_logout check that this_shell_builtin is also an exit attempt. (The code already sets both this_shell_builtin and last_shell_builtin to "exit" on the first exit attempt, so typing ^D twice in a row will exit immediately like it should.) Configuration Information: Machine: x86_64 OS: linux-gnu Compiler: gcc Compilation CFLAGS: -g -O2 -Wno-parentheses -Wno-format-security uname output: Linux debian 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64 GNU/Linux Machine Type: x86_64-pc-linux-gnu Bash Version: 5.0 Patch Level: 2 Release Status: release Regards, Tom Levy
>From c4fdfa2874685685a0ba46516173abf2b5ba43ef Mon Sep 17 00:00:00 2001 From: Tom Levy <> Date: Thu, 7 Mar 2019 04:58:18 +0000 Subject: [PATCH] fix ^D: don't exit while jobs are stopped after intervening command --- builtins/exit.def | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/builtins/exit.def b/builtins/exit.def index 5167b2e0..193a6a59 100644 --- a/builtins/exit.def +++ b/builtins/exit.def @@ -51,6 +51,7 @@ $END extern int check_jobs_at_exit; static int exit_or_logout __P((WORD_LIST *)); +static int is_exit_attempt __P((sh_builtin_func_t *)); static int sourced_logout; int @@ -103,9 +104,8 @@ exit_or_logout (list) int exit_immediate_okay, stopmsg; exit_immediate_okay = (interactive == 0 || - last_shell_builtin == exit_builtin || - last_shell_builtin == logout_builtin || - last_shell_builtin == jobs_builtin); + (is_exit_attempt (this_shell_builtin) && + is_exit_attempt (last_shell_builtin))); /* Check for stopped jobs if the user wants to. */ if (exit_immediate_okay == 0) @@ -155,6 +155,15 @@ exit_or_logout (list) /*NOTREACHED*/ } +static int +is_exit_attempt (function) + sh_builtin_func_t *function; +{ + return (function == exit_builtin || + function == logout_builtin || + function == jobs_builtin); +} + void bash_logout () { -- 2.11.0