modify way bash avoids running traps in subshells that haven't reset the trap strings yet; new readline bindable command 'shell-expand-and-requote-line'

This commit is contained in:
Chet Ramey
2026-01-14 11:08:22 -05:00
parent e7c3acf108
commit a6421d8419
13 changed files with 194 additions and 50 deletions
+29
View File
@@ -12512,3 +12512,32 @@ jobs.c
- wait_for_background_pids: treat wait_for_single_pid returning > 256
as an error, same as returning < 0, and check errno appropriately
Report from Aleksey Covacevice <aleksey.covacevice@gmail.com>
sig.c
- termsig_handler: don't try to run the exit trap if we're supposed
to be ignoring traps (until they're reset) in a subshell
environment
1/7
---
execute_cmd.c
- execute_in_subshell: set SUBSHELL_IGNTRAP before checking for any
fatal signal instead of calling clear_exit_trap(); this modifies
the change from 12/1
1/9
---
bashline.c
- shell_expand_line_internal: rename shell_expand_line(), takes an
additional argument saying whether or not to quote the individual
words in the expanded line (which changes how the line is split
initially, since shell-expand-line treats the line as a single
potentially-quoted word)
- shell_expand_line: call shell_expand_line_internal
- shell-expand-and-requote-line: new bindable command, splits the
line into individual words like with programmable completion,
expands each one, including word splitting, then quotes the
resultant words if necessary to prevent subsequent expansion
- shell_expand_and_requote_line: calls shell_expand_line_internal
with a third argument of 1 to force quoting
From a proposal by Koichi Murase <myoga.murase@gmail.com> in 2/2024
+79 -19
View File
@@ -1,6 +1,6 @@
/* bashline.c -- Bash's interface to the readline library. */
/* Copyright (C) 1987-2025 Free Software Foundation, Inc.
/* Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -117,7 +117,9 @@ extern int tputs (const char *string, int nlines, int (*outx)(int));
/* Forward declarations */
/* Functions bound to keys in Readline for Bash users. */
static int shell_expand_line_internal (int, int, int);
static int shell_expand_line (int, int);
static int shell_expand_and_requote_line (int, int);
static int display_shell_version (int, int);
static int bash_ignore_filenames (char **);
@@ -466,6 +468,7 @@ initialize_readline (void)
/* Add bindable names before calling rl_initialize so they may be
referenced in the various inputrc files. */
rl_add_defun ("shell-expand-line", shell_expand_line, -1);
rl_add_defun ("shell-expand-and-requote-line", shell_expand_and_requote_line, -1);
#ifdef BANG_HISTORY
rl_add_defun ("history-expand-line", history_expand_line, -1);
rl_add_defun ("magic-space", tcsh_magic_space, -1);
@@ -3005,11 +3008,12 @@ history_and_alias_expand_line (int count, int ignore)
}
/* History and alias expand the line, then perform the shell word
expansions by calling expand_string. This can't use set_up_new_line()
because we want the variable expansions as a separate undo'able
set of operations. */
expansions by calling expand_word(). If QUOTE_WORDS is non-zero,
we single-quote the expanded words if they contain any shell
metacharacters. This can't use set_up_new_line() because we want
the variable expansions as a separate undoable set of operations. */
static int
shell_expand_line (int count, int ignore)
shell_expand_line_internal (int count, int ignore, int quote_words)
{
char *new_line, *t;
WORD_LIST *expanded_string;
@@ -3048,17 +3052,25 @@ shell_expand_line (int count, int ignore)
/* If there is variable expansion to perform, do that as a separate
operation to be undone. */
#if 1
w = alloc_word_desc ();
w->word = savestring (rl_line_buffer);
w->flags = rl_explicit_arg ? (W_NOPROCSUB|W_NOCOMSUB) : 0;
expanded_string = expand_word (w, rl_explicit_arg ? Q_HERE_DOCUMENT : 0);
dispose_word (w);
#else
new_line = savestring (rl_line_buffer);
expanded_string = expand_string (new_line, 0);
FREE (new_line);
#endif
if (quote_words)
{
WORD_LIST *wl;
wl = split_at_delims (rl_line_buffer, strlen (rl_line_buffer), (char *)NULL, -1, 0, (int *)NULL, (int *)NULL);
if (rl_explicit_arg)
for (expanded_string = wl; expanded_string; expanded_string = expanded_string->next)
expanded_string->word->flags |= (W_NOPROCSUB|W_NOCOMSUB);
expanded_string = expand_words_shellexp (wl);
dispose_words (wl);
}
else
{
w = alloc_word_desc ();
w->word = savestring (rl_line_buffer);
w->flags = rl_explicit_arg ? (W_NOPROCSUB|W_NOCOMSUB) : 0;
expanded_string = expand_word (w, rl_explicit_arg ? Q_HERE_DOCUMENT : 0);
dispose_word (w);
}
if (expanded_string == 0)
{
@@ -3066,13 +3078,48 @@ shell_expand_line (int count, int ignore)
new_line[0] = '\0';
}
else
new_line = string_list (expanded_string);
/* We do it this way so we can make the expansion and (optional)
quoting separate undoable operations. */
maybe_make_readline_line (new_line);
free (new_line);
/* If requested, we quote the expanded words if they need it. This uses
split_at_delims in the same way that programmable completion does. */
if (quote_words)
{
char *nword;
WORD_LIST *wl;
for (wl = expanded_string; wl; wl = wl->next)
{
t = wl->word->word;
if (t == 0)
continue; /* XXX skip empty words */
nword = NULL;
if (*t == 0)
nword = sh_single_quote (t);
else if (ansic_shouldquote (t))
nword = ansic_quote (t, 0, (int *)0);
else if (rl_explicit_arg || sh_contains_shell_metas (t))
nword = sh_single_quote (t);
if (nword)
{
free (t);
wl->word->word = nword;
}
}
new_line = string_list (expanded_string);
dispose_words (expanded_string);
maybe_make_readline_line (new_line);
free (new_line);
}
maybe_make_readline_line (new_line);
free (new_line);
if (expanded_string)
dispose_words (expanded_string);
/* Place rl_point where we think it should go. */
if (at_end)
@@ -3092,6 +3139,19 @@ shell_expand_line (int count, int ignore)
}
}
static int
shell_expand_line (int count, int ignore)
{
return (shell_expand_line_internal (count, ignore, 0));
}
static int
shell_expand_and_requote_line (int count, int ignore)
{
return (shell_expand_line_internal (count, ignore, 1));
}
/* If FIGNORE is set, then don't match files with the given suffixes when
completing filenames. If only one of the possibilities has an acceptable
suffix, delete the others, else just return and let the completer
+2 -1
View File
@@ -1,7 +1,7 @@
/* command.h -- The structures used internally to represent commands, and
the extern declarations of the functions used to create them. */
/* Copyright (C) 1993-2022 Free Software Foundation, Inc.
/* Copyright (C) 1993-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -126,6 +126,7 @@ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select,
#define SUBSHELL_COPROC 0x40 /* subshell from a coproc pipeline */
#define SUBSHELL_RESETTRAP 0x80 /* subshell needs to reset trap strings on first call to trap */
#define SUBSHELL_IGNTRAP 0x100 /* subshell should reset trapped signals from trap_handler */
#define SUBSHELL_RESETJOBS 0x200 /* subshell should clear the jobs list */
/* A structure which represents a word. */
typedef struct word_desc {
+24 -5
View File
@@ -5,7 +5,7 @@
.\" Case Western Reserve University
.\" chet.ramey@case.edu
.\"
.\" Last Change: Wed Dec 31 18:30:12 EST 2025
.\" Last Change: Fri Jan 9 10:17:30 EST 2026
.\"
.\" For bash_builtins, strip all but "SHELL BUILTIN COMMANDS" section
.\" For rbash, strip all but "RESTRICTED SHELL" section
@@ -22,7 +22,7 @@
.ds zX \" empty
.if \n(zZ=1 .ig zZ
.if \n(zY=1 .ig zY
.TH BASH 1 "2025 December 31" "GNU Bash 5.3"
.TH BASH 1 "2026 January 9" "GNU Bash 5.3"
.\"
.ie \n(.g \{\
.ds ' \(aq
@@ -110,8 +110,8 @@ bash \- GNU Bourne-Again SHell
[options]
[command_string | file]
.SH COPYRIGHT
.if n Bash is Copyright (C) 1989-2025 by the Free Software Foundation, Inc.
.if t Bash is Copyright \(co 1989-2025 by the Free Software Foundation, Inc.
.if n Bash is Copyright (C) 1989-2026 by the Free Software Foundation, Inc.
.if t Bash is Copyright \(co 1989-2026 by the Free Software Foundation, Inc.
.SH DESCRIPTION
.B Bash
is a command language interpreter that
@@ -7761,7 +7761,8 @@ last word, as if the
history expansion had been specified.
.TP
.B shell\-expand\-line (M\-C\-e)
Expand the line by performing shell word expansions.
Expand the line by performing shell word expansions,
treating the line as a single shell word.
This performs alias and history expansion,
\fB$\fP\*'\fIstring\fP\*' and \fB$\fP\*"\fIstring\fP\*" quoting,
tilde expansion, parameter and variable expansion, arithmetic expansion,
@@ -7773,6 +7774,24 @@ See
.B "HISTORY EXPANSION"
below for a description of history expansion.
.TP
.B shell\-expand\-and\-requote\-line ()
Expand the line by performing shell word expansions,
splitting the line into shell words in the same way as for
programmable completion.
This performs alias and history expansion,
\fB$\fP\*'\fIstring\fP\*' and \fB$\fP\*"\fIstring\fP\*" quoting,
tilde expansion, parameter and variable expansion, arithmetic expansion,
command and process substitution,
word splitting, and quote removal
on each word, then quotes the resulting words if necessary to
prevent further expansion.
An explicit argument suppresses command and process substitution
and quotes each resultant word.
As usual, double-quoting a word will suppress word splitting.
This can be useful when combined with suppressing command substitution,
for instance, so the words in the command substitution aren't
quoted individually.
.TP
.B history\-expand\-line (M\-\*^)
Perform history expansion on the current line.
See
+4 -4
View File
@@ -1,11 +1,11 @@
@ignore
Copyright (C) 1988-2025 Free Software Foundation, Inc.
Copyright (C) 1988-2026 Free Software Foundation, Inc.
@end ignore
@set LASTCHANGE Wed Dec 31 18:26:37 EST 2025
@set LASTCHANGE Fri Jan 9 10:17:58 EST 2026
@set EDITION 5.3
@set VERSION 5.3
@set UPDATED 31 December 2025
@set UPDATED-MONTH December 2025
@set UPDATED 9 January 2026
@set UPDATED-MONTH January 2026
+3 -7
View File
@@ -1,6 +1,6 @@
/* execute_cmd.c -- Execute a COMMAND structure. */
/* Copyright (C) 1987-2025 Free Software Foundation, Inc.
/* Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -1680,13 +1680,13 @@ execute_in_subshell (COMMAND *command, int asynchronous, int pipe_in, int pipe_o
if (user_subshell)
{
subshell_environment = SUBSHELL_PAREN; /* XXX */
subshell_environment = SUBSHELL_PAREN|SUBSHELL_IGNTRAP; /* XXX */
if (asynchronous)
subshell_environment |= SUBSHELL_ASYNC;
}
else
{
subshell_environment = 0; /* XXX */
subshell_environment = SUBSHELL_IGNTRAP; /* XXX */
if (asynchronous)
subshell_environment |= SUBSHELL_ASYNC;
if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
@@ -1695,10 +1695,6 @@ execute_in_subshell (COMMAND *command, int asynchronous, int pipe_in, int pipe_o
subshell_environment |= SUBSHELL_COPROC;
}
/* clear the exit trap before checking for fatal signals, but don't free
the trap command (see below). */
clear_exit_trap (0);
QUIT;
CHECK_TERMSIG;
+19 -1
View File
@@ -1,6 +1,6 @@
/* general.c -- Stuff that is used by all files. */
/* Copyright (C) 1987-2025 Free Software Foundation, Inc.
/* Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -456,6 +456,24 @@ valid_function_word (WORD_DESC *word, int flags)
err_invalidid (name);
return (0);
}
#if 0 /*TAG: bash-5.4 kre@munnari.oz.au 6/11/2025 */
if (word->flags & W_QUOTED)
{
char *newname;
newname = string_quote_removal (name, 0);
if (newname == 0)
{
err_invalidid (name);
return (0);
}
free (word->word);
word->word = name = newname;
word->flags &= ~W_QUOTED;
}
#endif
/* POSIX interpretation 383 -- this is an application requirement, but the
shell should enforce it rather than allow a script to define a function
that will never be called. */
+1 -1
View File
@@ -3,7 +3,7 @@
/* This file works with both POSIX and BSD systems. It implements job
control. */
/* Copyright (C) 1989-2025 Free Software Foundation, Inc.
/* Copyright (C) 1989-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
+23 -3
View File
@@ -2065,13 +2065,33 @@ If a numeric argument is supplied, append a @samp{*} before
pathname expansion.
@item shell-expand-line (M-C-e)
Expand the line by performing shell word expansions.
Expand the line by performing shell word expansions,
treating the line as a single shell word.
This performs alias and history expansion,
$'@var{string}' and $"@var{string}" quoting,
tilde expansion, parameter and variable expansion, arithmetic expansion,
command and process substitution,
word splitting, and quote removal.
An explicit argument suppresses command and process substitution.
word splitting, and quote removal.
An explicit argument suppresses command and process substitution and
treats the line as if it were quoted as part of a here-document.
@item shell-expand-and-requote-line ()
Expand the line by performing shell word expansions,
splitting the line into shell words in the same way as for
programmable completion.
This performs alias and history expansion,
$'@var{string}' and $"@var{string}" quoting,
tilde expansion, parameter and variable expansion, arithmetic expansion,
command and process substitution,
word splitting, and quote removal
on each word, then quotes the resulting words if necessary to
prevent further expansion.
An explicit argument suppresses command and process substitution
and quotes each resultant word.
As usual, double-quoting a word will suppress word splitting.
This can be useful when combined with suppressing command substitution,
for instance, so the words in the command substitution aren't
quoted individually.
@item history-expand-line (M-^)
Perform history expansion on the current line.
+5 -2
View File
@@ -1,6 +1,6 @@
/* sig.c - interface for shell signal handlers and signal initialization. */
/* Copyright (C) 1994-2024 Free Software Foundation, Inc.
/* Copyright (C) 1994-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -644,7 +644,10 @@ termsig_handler (int sig)
interrupt_execution = retain_fifos = executing_funsub = 0;
comsub_ignore_return = return_catch_flag = wait_intr_flag = 0;
run_exit_trap (); /* XXX - run exit trap possibly in signal context? */
/* Don't run the exit trap if we're supposed to be ignoring traps in a
subshell environment. */
if ((subshell_environment & SUBSHELL_IGNTRAP) == 0)
run_exit_trap (); /* XXX - run exit trap possibly in signal context? */
kill_shell (sig);
}
+2 -4
View File
@@ -4,7 +4,7 @@
/* ``Have a little faith, there's magic in the night. You ain't a
beauty, but, hey, you're alright.'' */
/* Copyright (C) 1987-2025 Free Software Foundation, Inc.
/* Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -7143,10 +7143,8 @@ function_substitute (char *string, int quoted, int flags)
add_unwind_protect (uw_unbind_localvar, "REPLY");
}
#if 1 /* TAG:bash-5.3 myoga.murase@gmail.com 04/30/2024 */
old_frozen = freeze_jobs_list (-1);
add_unwind_protect (uw_lastpipe_cleanup, (void *) (intptr_t) old_frozen);
#endif
#if defined (JOB_CONTROL)
unwind_protect_var (pipeline_pgrp);
@@ -8768,7 +8766,7 @@ string_var_assignment (SHELL_VAR *v, char *s)
sprintf (ret, "declare -%s %s", flags, v->name); /* just attributes, unset */
else if (i > 0)
sprintf (ret, "declare -%s %s=%s", flags, v->name, val); /* attributes, set */
#if 1 /*TAG: bash-5.3 tentative */
#if 1 /*TAG: bash-5.4 tentative */
else if (i == 0 && val && local_p (v) && variable_context == v->context)
sprintf (ret, "declare %s=%s", v->name, val); /* set local variable at current scope */
else if (i == 0 && val == 0 && local_p (v) && variable_context == v->context)
+1 -1
View File
@@ -16,7 +16,7 @@
# this locale causes problems all over the place
if [ -z "$ZH_LOCALE" ]; then
echo "${CSTART}glob2.sub: warning${CEND}: you do not have the zh_TW.big5 locale installed;" >&2
echo "${TAB}glob2.sub: that may cause some of these tests to fail." >&2
echo "${TAB}that may cause some of these tests to fail." >&2
ZH_LOCALE=$ZH_DEFAULT
fi
+2 -2
View File
@@ -1,6 +1,6 @@
/* variables.c -- Functions for hacking shell variables. */
/* Copyright (C) 1987-2025 Free Software Foundation, Inc.
/* Copyright (C) 1987-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -455,7 +455,7 @@ initialize_shell_variables (char **env, int privmode)
STREQN (BASHARRAY_SUFFIX, name + char_index - BASHARRAY_SUFFLEN, BASHARRAY_SUFFLEN) &&
*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
{
size_t namelen, slen;
size_t namelen;
char *tname; /* desired imported array variable name */
namelen = char_index - BASHARRAY_PREFLEN - BASHARRAY_SUFFLEN;