From: Chet Ramey
Date: Fri, 5 Dec 2025 20:50:38 +0000 (-0500)
Subject: implementation of printf '%N$' numbered argument conversion specifier, compatible...
X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=f27bf94a7927219f96e97e1a21f2ade9dd439a3e;p=thirdparty%2Fbash.git
implementation of printf '%N$' numbered argument conversion specifier, compatible with coreutils
---
diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog
index 65f4c95b..ab59fcd1 100644
--- a/CWRU/CWRU.chlog
+++ b/CWRU/CWRU.chlog
@@ -12311,3 +12311,19 @@ execute_cmd.c
- execute_in_subshell: clear the exit trap before checking for a
pending fatal signal
From https://savannah.gnu.org/bugs/?67745
+
+ 12/2
+ ----
+builtins/printf.def
+ - import %N$ specifier implementation from printf-nspecifier branch;
+ implementation is similar to coreutils'.
+ - we warn about mixing numbered and unnumbered format specifiers in
+ posix mode
+ - getint: now takes second argument to deal with numbered specifiers
+ - getstar: new function to get precision and fieldwidth from argument
+ list if `*' in format specifier, handling numbered arguments
+ - narg, nptr: we build a new format string as necessary
+ Originally from https://savannah.gnu.org/support/?111166, also
+ discussed in thread starting with
+ https://lists.gnu.org/archive/html/bug-bash/2025-02/msg00151.html
+
diff --git a/MANIFEST b/MANIFEST
index d1b00de7..f9af056b 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1442,6 +1442,7 @@ tests/printf4.sub f
tests/printf5.sub f
tests/printf6.sub f
tests/printf7.sub f
+tests/printf8.sub f
tests/procsub.tests f
tests/procsub.right f
tests/procsub1.sub f
diff --git a/builtins/printf.def b/builtins/printf.def
index 2bb70581..b3622017 100644
--- a/builtins/printf.def
+++ b/builtins/printf.def
@@ -100,6 +100,8 @@ extern int errno;
{ \
QUIT; \
retval = value; \
+ if (narg_argc != -1) \
+ free (narg_argv); \
if (conv_bufsize > 4096 ) \
{ \
free (conv_buf); \
@@ -178,6 +180,10 @@ extern int errno;
#define LENMODS "hjlLtz"
#define DIGITS "0123456789"
+#ifndef NL_ARGMAX
+# define NL_ARGMAX 999
+#endif
+
#ifndef TIMELEN_MAX
# define TIMELEN_MAX 128
#endif
@@ -202,7 +208,8 @@ static int vbprintf (const char *, ...) __attribute__((__format__ (printf, 1, 2)
static char *mklong (char *, char *, size_t);
static int getchr (void);
static char *getstr (void);
-static int getint (int);
+static int getint (int, int);
+static int getstar (char **, int);
static intmax_t getintmax (void);
static uintmax_t getuintmax (void);
@@ -247,12 +254,42 @@ static size_t vblen;
static char **narg_argv;
static int narg_argc;
static int narg_maxind;
+static int narg_numind; /* last numbered argument specification */
+static int narg_seqind; /* only used when mixing numbered and unnumbered conversions */
+static int narg_base; /* used on format reuse */
+static char *narg_arg; /* argument corresponding to the numbered conversion spec */
+static int narg_convtype; /* 1 = numbered, 0 = unnumbered */
+static int narg_convwarned; /* want to minimize the warnings */
+static char *nfmt = 0;
static intmax_t tw;
static char *conv_buf;
static size_t conv_bufsize;
+static void
+init_numarg ()
+{
+ size_t len;
+ WORD_LIST *l;
+
+ len = list_length ((GENERIC_LIST *)orig_arglist);
+ narg_argv = (char **)xmalloc ((len + 2) * sizeof (char *)); /* +2 because we don't use 0 */
+
+ for (narg_argc = 1, l = orig_arglist; l; l = l->next)
+ narg_argv[narg_argc++] = l->word->word;
+
+ /* If we've processed some unnumbered conversion specifications before
+ we get the first numbered one, count those as "previous conversion
+ specifications that consumed an argument." */
+ for (narg_seqind = 0, l = orig_arglist; l != garglist; l = l->next)
+ narg_seqind++;
+
+ narg_argv[narg_argc] = NULL;
+ narg_maxind = narg_numind = narg_base = 0;
+ narg_arg = NULL;
+}
+
static inline int
decodeint (char **str, int diagnose, int overflow_return)
{
@@ -281,7 +318,10 @@ printf_builtin (WORD_LIST *list)
{
int ch, fieldwidth, precision;
int have_fieldwidth, have_precision, use_Lmod, altform, longform;
+ int moreargs;
char convch, thisch, nextch, *format, *modstart, *precstart, *fmt, *start;
+ char *nptr, *origfmt;
+ size_t nargind;
#if defined (HANDLE_MULTIBYTE)
char mbch[25]; /* 25 > MB_LEN_MAX, plus can handle 4-byte UTF-8 and large Unicode characters*/
int mbind, mblen, mb_cur_max;
@@ -293,6 +333,8 @@ printf_builtin (WORD_LIST *list)
conversion_error = 0;
vflag = 0;
+ narg_argc = -1;
+
reset_internal_getopt ();
while ((ch = internal_getopt (list, "v:")) != -1)
{
@@ -357,15 +399,23 @@ printf_builtin (WORD_LIST *list)
mb_cur_max = MB_CUR_MAX;
+ nfmt = xrealloc (nfmt, strlen (format) + 1); /* XXX error checking */
+ nfmt[0] = '\0';
+
/* Basic algorithm is to scan the format string for conversion
specifications -- once one is found, find out if the field
width or precision is a '*'; if it is, gather up value. Note,
format strings are reused as necessary to use up the provided
arguments, arguments of zero/null string are provided to use
up the format string. */
+
+ /* We only warn once, even if we reuse the format, and we only warn in
+ posix mode. */
+ narg_convwarned = !posixly_correct;
do
{
tw = 0;
+
/* find next format specification */
for (fmt = format; *fmt; fmt++)
{
@@ -409,7 +459,9 @@ printf_builtin (WORD_LIST *list)
}
/* ASSERT(*fmt == '%') */
- start = fmt++;
+ origfmt = fmt; /* saved for format errors */
+ nptr = start = nfmt; /* we construct our own format string */
+ *nptr++ = *fmt++;
if (*fmt == '%') /* %% prints a % */
{
@@ -417,37 +469,117 @@ printf_builtin (WORD_LIST *list)
continue;
}
+ narg_convtype = 0;
+ /* Look for possible %N$ numbered conversion specifier. */
+ /* "Conversions can be applied to the nth argument operand rather
+ than to the next argument operand. In this case, the conversion
+ specifier character '%' is replaced by the sequence "%n$",
+ where n is a decimal integer in the range [1,{NL_ARGMAX}],
+ giving the argument operand number. This feature provides for
+ the definition of format strings that select arguments in an
+ order appropriate to specific languages." */
+ /* This leaves nargv_curind pointing to the argument corresponding
+ to the numbered conversion spec or the next sequential one after
+ having processed a numbered spec, so the advancearg() in the
+ various code handling the conversion specifiers advances it. */
+ /* We don't allow N$ for precision or field width at this time. */
+ nargind = strspn (fmt, DIGITS);
+ if (nargind > 0 && fmt[nargind] == '$')
+ {
+ char *ep;
+ int narg, thisarg;
+
+ if (garglist != orig_arglist && narg_convwarned == 0)
+ {
+ builtin_warning ("%s", _("should not mix numbered and unnumbered conversions"));
+ narg_convwarned = 1;
+ }
+ if (narg_argc == -1)
+ init_numarg ();
+ thisarg = (int)strtol (fmt, &ep, 10);
+ /* "If it is a numbered argument conversion specification,
+ printf should write a diagnostic message to standard error
+ and exit with non-zero status" */
+ if (thisarg <= 0 || thisarg >= narg_argc)
+ {
+ /* We don't want to print this error message for numbered
+ conversions exceeding the number of arguments unless
+ we are in posix mode, so we set narg_numind = narg_argc
+ if we are not. */
+ if (thisarg <= 0 || posixly_correct)
+ {
+ builtin_error (_("%d: numbered conversion out of range"), thisarg);
+ PRETURN (EXECUTION_FAILURE);
+ }
+ thisarg = narg_argc;
+ }
+ thisarg += narg_base;
+ narg_numind = (thisarg < narg_argc) ? thisarg : narg_argc;
+ if (narg_numind > narg_maxind)
+ narg_maxind = narg_numind;
+ narg_arg = narg_argv[narg_numind];
+ narg_convtype = 1;
+ fmt += nargind + 1;
+ }
+ else if (narg_argc != -1)
+ {
+ int thisarg;
+
+ if (narg_convwarned == 0)
+ {
+ builtin_warning ("%s", _("should not mix numbered and unnumbered conversions"));
+ narg_convwarned = 1;
+ }
+ /* There is genuine incompatibility here between macOS/FreeBSD
+ printf and coreutils printf.
+ Given '%s %3$s %s\n' A B C D, coreutils printf treats the
+ unnumbered coversion specs sequentially, so it echoes "A C B",
+ then "D" on a new line.
+ FreeBSD printf treats the next unnumbered specifier following
+ a numbered specifier as numbered + 1, and prints "A C D".
+ zsh and ksh93 are like coreutils; mksh is like FreeBSD.
+ We follow coreutils here for now, but subject to change. */
+ narg_arg = (narg_seqind < narg_argc) ? narg_argv[++narg_seqind] : NULL;
+ if (narg_seqind > narg_maxind)
+ narg_maxind = narg_seqind;
+ }
+
/* Found format specification, skip to field width. We check for
alternate form for possible later use. */
- for (; *fmt && strchr(SKIP1, *fmt); ++fmt)
- if (*fmt == '#')
- altform++;
+ while (*fmt && strchr(SKIP1, *fmt))
+ {
+ if (*fmt == '#')
+ altform++;
+ *nptr++ = *fmt++; /* build format string */
+ }
+ *nptr = '\0';
/* Skip optional field width. */
if (*fmt == '*')
{
- fmt++;
+ *nptr++ = *fmt++;
have_fieldwidth = 1;
/* Handle field with overflow by ignoring fieldwidth for now.
- getint() prints a message. */
- fieldwidth = getint (0);
+ getstar() prints a message. */
+ fieldwidth = getstar (&fmt, 0);
}
else
while (DIGIT (*fmt))
- fmt++;
+ *nptr++ = *fmt++;
+ *nptr = '\0';
/* Skip optional '.' and precision */
if (*fmt == '.')
{
- ++fmt;
+ *nptr++ = *fmt++;
if (*fmt == '*')
{
- fmt++;
+ *nptr++ = *fmt++;
have_precision = 1;
/* Handle precision overflow by ignoring precision for now.
- getint() prints a message.
+ getstar() prints a message.
"A negative precision is treated as if it were missing." */
- precision = getint (-1);
+ precision = getstar (&fmt, -1);
}
else
{
@@ -460,12 +592,13 @@ printf_builtin (WORD_LIST *list)
#else
if (*fmt == '-')
#endif
- fmt++;
+ *nptr++ = *fmt++;
if (DIGIT (*fmt))
precstart = fmt;
while (DIGIT (*fmt))
- fmt++;
+ *nptr++ = *fmt++;
}
+ *nptr = '\0';
}
/* skip possible format modifiers */
@@ -475,7 +608,7 @@ printf_builtin (WORD_LIST *list)
{
use_Lmod |= USE_LONG_DOUBLE && *fmt == 'L';
longform |= *fmt == 'l';
- fmt++;
+ *nptr++ = *fmt++;
}
if (*fmt == 0)
@@ -484,6 +617,9 @@ printf_builtin (WORD_LIST *list)
PRETURN (EXECUTION_FAILURE);
}
+ *nptr++ = *fmt;
+ *nptr = '\0';
+
convch = *fmt;
thisch = modstart[0];
nextch = modstart[1];
@@ -607,7 +743,7 @@ printf_builtin (WORD_LIST *list)
if (*fmt != ')' || *++fmt != 'T')
{
builtin_warning (_("`%c': invalid time format specification"), *fmt);
- fmt = start;
+ fmt = origfmt;
free (timefmt);
PC (*fmt);
continue;
@@ -620,7 +756,10 @@ printf_builtin (WORD_LIST *list)
}
/* argument is seconds since the epoch with special -1 and -2 */
/* default argument is equivalent to -1; special case */
- arg = garglist ? getintmax () : -1;
+ if (narg_argc != -1)
+ arg = (narg_numind < narg_argc && narg_argv[narg_numind]) ? getintmax (): -1;
+ else
+ arg = garglist ? getintmax () : -1;
if (arg == -1)
secs = NOW; /* roughly date +%s */
else if (arg == -2)
@@ -643,8 +782,8 @@ printf_builtin (WORD_LIST *list)
else
timebuf[sizeof(timebuf) - 1] = '\0';
/* convert to %s format that preserves fieldwidth and precision */
- modstart[0] = 's';
- modstart[1] = '\0';
+ *nptr++ = 's';
+ *nptr = '\0';
r = printstr (start, timebuf, strlen (timebuf), fieldwidth, precision); /* XXX - %s for now */
if (r < 0)
PRETURN (EXECUTION_FAILURE);
@@ -821,8 +960,44 @@ printf_builtin (WORD_LIST *list)
/* PRETURN will print error message. */
PRETURN (EXECUTION_FAILURE);
}
+
+ /* "The format operand shall be reused as often as necessary to satisfy
+ the argument operands. If conversion specifications beginning with
+ a "%n$" sequence are used, on format reuse the value of n shall
+ refer to the nth argument operand following the highest numbered
+ argument operand consumed by the previous use of the format operand." */
+ if (narg_argc != -1)
+ {
+ /* If we consumed the last argument, we're done. */
+ moreargs = (narg_numind < narg_argc) && (narg_maxind < narg_argc) && (narg_seqind < narg_argc);
+ narg_base = narg_maxind;
+ narg_maxind = 0; /* need to recalculate this */
+ }
+ else
+ moreargs = garglist && garglist != list->next;
+
+ /* "on format reuse the value of n shall refer to the nth argument
+ operand following the highest numbered argument operand consumed
+ by the previous use of the format operand." */
+ /* This mess is to handle combining numbered and unnumbered conversion
+ specifiers. */
+ if (moreargs && narg_argc != -1)
+ {
+ /* If we decide to treat numbered and unnumbered specifiers with
+ different counters. */
+ if (garglist == 0 && orig_arglist != 0)
+ moreargs = 0;
+ /* I don't like this -- POSIX says "previous conversion specification
+ that consumed an argument", not "highest-numbered argument
+ processed" -- but this is what coreutils printf seems to do. */
+ narg_seqind = narg_base;
+ /* The second clause will be true if we processed the last
+ argument (not necessarily all arguments). */
+ if (narg_seqind >= narg_argc || (narg_base + 1) >= narg_argc)
+ moreargs = 0;
+ }
}
- while (garglist && garglist != list->next);
+ while (moreargs);
if (conversion_error)
retval = EXECUTION_FAILURE;
@@ -1346,13 +1521,25 @@ mklong (char *str, char *modifiers, size_t mlen)
static inline char *
getarg (void)
{
+ if (narg_argc != -1)
+ return narg_arg;
+
return (garglist ? garglist->word->word : 0);
}
static inline void
advancearg (void)
{
- garglist = garglist->next;
+ if (narg_argc != -1)
+ {
+#if 0
+ /* see if we need to manage narg_seqind here or in printf_builtin */
+ if (narg_numind < narg_argc)
+ narg_numind++;
+#endif
+ }
+ else
+ garglist = garglist->next;
}
static int
@@ -1404,15 +1591,19 @@ chk_converror (char *s, char *ep)
}
/* Don't call getintmax here because it may consume an argument on error, and
- we call this to get field width/precision arguments. */
+ we call this to get field width/precision arguments. This is only called
+ by getstar() to get field width/precision values from arguments. It does
+ not call getarg() and it does not advance the argument with advancearg. */
static int
-getint (int overflow_retval)
+getint (int numberedconv, int overflow_retval)
{
intmax_t ret;
char *ep, *arg;
int overflow;
- arg = getarg ();
+ /* XXX - check here that narg_numind < narg_argc and return null in that case? */
+ arg = (narg_argc != -1) ? (numberedconv ? narg_argv[narg_numind] : narg_argv[narg_seqind])
+ : (garglist ? garglist->word->word : 0);
if (arg == 0)
return (0);
@@ -1427,10 +1618,63 @@ getint (int overflow_retval)
chk_converror (arg, ep);
- advancearg ();
return (overflow ? overflow_retval : (int)ret);
}
+static int
+getstar (char **fmtp, int overflow_retval)
+{
+ int ret, numconv;
+ char *ep, **fp;
+ size_t l, ind;
+
+ fp = fmtp;
+
+ numconv = 0;
+ ep = *fp;
+ l = DIGIT (**fp) ? strspn (ep, DIGITS) : 0;
+ if (l > 0 && ep[l] == '$')
+ {
+ if (narg_argc == -1)
+ return overflow_retval;
+ ind = decodeint (fp, 1, -1);
+ if (**fp == '$')
+ {
+ ep = *fp + 1;
+ fp = &ep;
+ }
+ *fmtp = *fp;
+ ind += narg_base;
+ if (ind > 0 && ind <= narg_argc) /* can't have 0-based indices */
+ narg_numind = ind;
+ else
+ return overflow_retval;
+ numconv = 1;
+ if (narg_numind > narg_maxind)
+ narg_maxind = narg_numind;
+ }
+ else if (narg_argc != -1)
+ {
+ if (narg_convwarned == 0)
+ {
+ builtin_warning ("%s", _("should not mix numbered and unnumbered conversions"));
+ narg_convwarned = 1;
+ }
+ /* We manage the sequential index here and in printf_builtin */
+ if (narg_seqind < narg_argc)
+ ++narg_seqind;
+ if (narg_seqind > narg_maxind)
+ narg_maxind = narg_seqind;
+ }
+
+ ret = getint (numconv, overflow_retval);
+
+ if (narg_argc == -1)
+ advancearg ();
+
+ return ret;
+}
+
static intmax_t
getintmax (void)
{
diff --git a/doc/bash.0 b/doc/bash.0
index da4d7d8f..1549badb 100644
--- a/doc/bash.0
+++ b/doc/bash.0
@@ -1689,6 +1689,9 @@ PPAARRAAMMEETTEERRSS
The "+=" operator appends to an array variable when assigning using the
compound assignment syntax; see PPAARRAAMMEETTEERRSS above.
+ If one of the word expansions in a compound array assignment unsets the
+ variable, the results are unspecified.
+
An array element is referenced using ${_n_a_m_e[_s_u_b_s_c_r_i_p_t]}. The braces
are required to avoid conflicts with pathname expansion. If _s_u_b_s_c_r_i_p_t
is @@ or **, the word expands to all members of _n_a_m_e, unless noted in the
@@ -1899,7 +1902,7 @@ EEXXPPAANNSSIIOONN
_p_a_r_a_m_e_t_e_r is a positional parameter with more than one digit, or when
_p_a_r_a_m_e_t_e_r is followed by a character which is not to be interpreted as
part of its name. The _p_a_r_a_m_e_t_e_r is a shell parameter as described
- above PPAARRAAMMEETTEERRSS) or an array reference (AArrrraayyss).
+ above (PPAARRAAMMEETTEERRSS) or an array reference (AArrrraayyss).
If the first character of _p_a_r_a_m_e_t_e_r is an exclamation point (!!), and
_p_a_r_a_m_e_t_e_r is not a _n_a_m_e_r_e_f, it introduces a level of indirection. BBaasshh
@@ -3729,6 +3732,7 @@ RREEAADDLLIINNEE
form
sseett _v_a_r_i_a_b_l_e_-_n_a_m_e _v_a_l_u_e
+
or using the bbiinndd builtin command (see SSHHEELLLL BBUUIILLTTIINN CCOOMMMMAANNDDSS below).
Except where noted, rreeaaddlliinnee variables can take the values OOnn or OOffff
@@ -4829,7 +4833,7 @@ RREEAADDLLIINNEE
fined, programmable completion performs rreeaaddlliinnee's default completion.
The options supplied to ccoommpplleettee and ccoommppoopptt can control how rreeaaddlliinnee
- treats the completions. For instance, the _-_o _f_u_l_l_q_u_o_t_e option tells
+ treats the completions. For instance, the --oo ffuullllqquuoottee option tells
rreeaaddlliinnee to quote the matches as if they were filenames. See the de-
scription of ccoommpplleettee below for details.
@@ -5745,8 +5749,8 @@ SSHHEELLLL BBUUIILLTTIINN CCOOMMMMAANNDDSS
directories in which to search for _f_i_l_e_n_a_m_e. The default for
BBAASSHH__LLOOAADDAABBLLEESS__PPAATTHH is system-dependent, and may include "." to
force a search of the current directory. The --dd option will
- delete a builtin previously loaded with --ff. If _-_s is used with
- _-_f, the new builtin becomes a POSIX special builtin.
+ delete a builtin previously loaded with --ff. If --ss is used with
+ --ff, the new builtin becomes a POSIX special builtin.
If no options are supplied and a _n_a_m_e is not a shell builtin,
eennaabbllee will attempt to load _n_a_m_e from a shared object named
@@ -6164,7 +6168,7 @@ SSHHEELLLL BBUUIILLTTIINN CCOOMMMMAANNDDSS
last.
If the top element of the directory stack is modified, and the
- _-_n option was not supplied, ppooppdd uses the ccdd builtin to change
+ --nn option was not supplied, ppooppdd uses the ccdd builtin to change
to the directory at the top of the stack. If the ccdd fails, ppooppdd
returns a non-zero value.
@@ -6232,12 +6236,27 @@ SSHHEELLLL BBUUIILLTTIINN CCOOMMMMAANNDDSS
is the numeric value of the following character, using the cur-
rent locale.
- The _f_o_r_m_a_t is reused as necessary to consume all of the _a_r_g_u_-
+ Format specifiers may apply to the _nth argument rather than the
+ next sequential argument. In this case, the '%' in the format
+ specifier is replaced by the sequence "%_n$", where _n is a deci-
+ mal integer greater than 0, giving the argument number to use as
+ the operand. The format string should not mix numbered and un-
+ numbered argument specifiers, though this is allowed. Unnum-
+ bered argument specifiers always refer to the next argument fol-
+ lowing the last argument consumed by an unnumbered specifier;
+ numbered argument specifiers refer to absolute positions in the
+ argument list.
+
+ The _f_o_r_m_a_t is reused as necessary to consume all of the _a_r_g_u_-
_m_e_n_t_s. If the _f_o_r_m_a_t requires more _a_r_g_u_m_e_n_t_s than are supplied,
- the extra format specifications behave as if a zero value or
- null string, as appropriate, had been supplied. The return
- value is zero on success, non-zero if an invalid option is sup-
- plied or a write or assignment error occurs.
+ the extra format specifications behave as if a zero value or
+ null string, as appropriate, had been supplied. If _f_o_r_m_a_t is
+ reused, a numbered argument specifier "%_n$" refers to the _nth
+ argument following the highest numbered argument consumed by the
+ previous use of _f_o_r_m_a_t.
+
+ The return value is zero on success, non-zero if an invalid op-
+ tion is supplied or a write or assignment error occurs.
ppuusshhdd [--nn] [+_n] [-_n]
ppuusshhdd [--nn] [_d_i_r]
@@ -7555,4 +7574,4 @@ BBUUGGSS
Array variables may not (yet) be exported.
-GNU Bash 5.3 2025 October 6 _B_A_S_H(1)
+GNU Bash 5.3 2025 December 2 _B_A_S_H(1)
diff --git a/doc/bash.1 b/doc/bash.1
index afe320ee..4554754e 100644
--- a/doc/bash.1
+++ b/doc/bash.1
@@ -5,7 +5,7 @@
.\" Case Western Reserve University
.\" chet.ramey@case.edu
.\"
-.\" Last Change: Mon Nov 17 11:37:04 EST 2025
+.\" Last Change: Tue Dec 2 16:43:36 EST 2025
.\"
.\" 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 November 17" "GNU Bash 5.3"
+.TH BASH 1 "2025 December 2" "GNU Bash 5.3"
.\"
.ie \n(.g \{\
.ds ' \(aq
@@ -11148,10 +11148,28 @@ except that a leading plus or minus sign is allowed, and if the leading
character is a single or double quote, the value is the numeric value of
the following character, using the current locale.
.IP
+Format specifiers may apply to the \fIn\fPth argument rather than the next
+sequential argument.
+In this case, the '%' in the format specifier is replaced by the
+sequence
+.Q %\fIn\fP$ ,
+where \fIn\fP is a decimal integer greater than 0,
+giving the argument number to use as the operand.
+The format string should not mix numbered and unnumbered argument specifiers,
+though this is allowed.
+Unnumbered argument specifiers always refer to the next argument following
+the last argument consumed by an unnumbered specifier; numbered argument
+specifiers refer to absolute positions in the argument list.
+.IP
The \fIformat\fP is reused as necessary to consume all of the \fIarguments\fP.
If the \fIformat\fP requires more \fIarguments\fP than are supplied, the
extra format specifications behave as if a zero value or null string, as
appropriate, had been supplied.
+If \fIformat\fP is reused, a numbered argument specifier
+.Q %\fIn\fP$
+refers to the \fIn\fPth argument following the highest numbered argument
+consumed by the previous use of \fIformat\fP.
+.IP
The return value is zero on success,
non-zero if an invalid option is supplied or a write or assignment error
occurs.
diff --git a/doc/bash.html b/doc/bash.html
index 46a6da12..14e4e194 100644
--- a/doc/bash.html
+++ b/doc/bash.html
@@ -1,5 +1,5 @@
-
+
@@ -3736,6 +3736,10 @@ an index of −1 references the last element.
assigning using the compound assignment syntax; see
PARAMETERS above.
+If one of the
+word expansions in a compound array assignment unsets the
+variable, the results are unspecified.
+
An array element
is referenced using ${name[subscript]}. The
braces are required to avoid conflicts with pathname
@@ -4056,7 +4060,7 @@ required when parameter is a positional parameter
with more than one digit, or when parameter is
followed by a character which is not to be interpreted as
part of its name. The parameter is a shell parameter
-as described above PARAMETERS) or an array reference
+as described above (PARAMETERS) or an array reference
(Arrays).
If the first
@@ -7793,9 +7797,9 @@ with a statement of the form
set
variable−name value
-or using the bind builtin
-command (see SHELL BUILTIN COMMANDS
-below).
+or using the
+bind builtin command (see SHELL BUILTIN
+COMMANDS below).
Except where
noted, readline variables can take the values
@@ -9875,7 +9879,7 @@ completion.
The options
supplied to complete and compopt can control
how readline treats the completions. For instance,
-the −o fullquote option tells readline
+the −o fullquote option tells readline
to quote the matches as if they were filenames. See the
description of complete below for details.
@@ -10537,9 +10541,10 @@ interpretation.
All builtins
except :, true, false, echo, and
-test/[ accept --help as a special
-option. If --help is supplied, these builtins output
-a help message and exit with a status of 0.
+test/[ accept −−help as a
+special option. If −−help is supplied,
+these builtins output a help message and exit with a status
+of 0.
: [arguments]
No effect; the command does
@@ -12119,8 +12124,8 @@ The default for BASH_LOADABLES_PATH is
system-dependent, and may include “.” to force a
search of the current directory. The −d option
will delete a builtin previously loaded with
-−f. If −s is used with
-−f, the new builtin becomes a
+−f. If −s is used with
+−f, the new builtin becomes a
POSIX special builtin.
If no options
@@ -13011,7 +13016,7 @@ directory, “popd −1” the next to last.
If the top
element of the directory stack is modified, and the
-−n option was not supplied, popd uses
+−n option was not supplied, popd uses
the cd builtin to change to the directory at the top
of the stack. If the cd fails, popd returns a
non-zero value.
@@ -13131,14 +13136,33 @@ the leading character is a single or double quote, the value
is the numeric value of the following character, using the
current locale.
+Format
+specifiers may apply to the nth argument rather than
+the next sequential argument. In this case, the
+’%’ in the format specifier is replaced by the
+sequence “%n$”, where n is a
+decimal integer greater than 0, giving the argument number
+to use as the operand. The format string should not mix
+numbered and unnumbered argument specifiers, though this is
+allowed. Unnumbered argument specifiers always refer to the
+next argument following the last argument consumed by an
+unnumbered specifier; numbered argument specifiers refer to
+absolute positions in the argument list.
+
The
format is reused as necessary to consume all of the
arguments. If the format requires more
arguments than are supplied, the extra format
specifications behave as if a zero value or null string, as
-appropriate, had been supplied. The return value is zero on
-success, non-zero if an invalid option is supplied or a
-write or assignment error occurs.
+appropriate, had been supplied. If format is reused,
+a numbered argument specifier “%n$”
+refers to the nth argument following the highest
+numbered argument consumed by the previous use of
+format.
+
+The return
+value is zero on success, non-zero if an invalid option is
+supplied or a write or assignment error occurs.
pushd [−n]
[+n] [−n]
diff --git a/doc/bash.info b/doc/bash.info
index 17059200..3143f1c2 100644
--- a/doc/bash.info
+++ b/doc/bash.info
@@ -1,9 +1,9 @@
This is bash.info, produced by makeinfo version 7.2 from bashref.texi.
This text is a brief description of the features that are present in the
-Bash shell (version 5.3, 24 September 2025).
+Bash shell (version 5.3, 2 December 2025).
- This is Edition 5.3, last updated 24 September 2025, of âThe GNU Bash
+ This is Edition 5.3, last updated 2 December 2025, of âThe GNU Bash
Reference Manualâ, for âBashâ, Version 5.3.
Copyright © 1988-2025 Free Software Foundation, Inc.
@@ -26,10 +26,10 @@ Bash Features
*************
This text is a brief description of the features that are present in the
-Bash shell (version 5.3, 24 September 2025). The Bash home page is
+Bash shell (version 5.3, 2 December 2025). The Bash home page is
.
- This is Edition 5.3, last updated 24 September 2025, of âThe GNU Bash
+ This is Edition 5.3, last updated 2 December 2025, of âThe GNU Bash
Reference Manualâ, for âBashâ, Version 5.3.
Bash contains features that appear in other popular shells, and some
@@ -4369,7 +4369,7 @@ standard.
The â-fâ option means to load the new builtin command NAME from
shared object FILENAME, on systems that support dynamic loading.
- If FILENAME does not contain a slash. Bash will use the value of
+ If FILENAME does not contain a slash, Bash will use the value of
the âBASH_LOADABLES_PATHâ variable as a colon-separated list of
directories in which to search for FILENAME. The default for
âBASH_LOADABLES_PATHâ is system-dependent, and may include "." to
@@ -4561,12 +4561,25 @@ standard.
the numeric value of the following character, using the current
locale.
+ Format specifiers may apply to the Nth argument rather than the
+ next sequential argument. In this case, the â%â in the format
+ specifier is replaced by the sequence â%N$â, where N is a decimal
+ integer greater than 0, giving the argument number to use as the
+ operand. The format string should not mix numbered and unnumbered
+ argument specifiers, though this is allowed. Unnumbered argument
+ specifiers always refer to the next argument following the last
+ argument consumed by an unnumbered specifier; numbered argument
+ specifiers refer to absolute positions in the argument list.
+
The FORMAT is reused as necessary to consume all of the ARGUMENTS.
If the FORMAT requires more ARGUMENTS than are supplied, the extra
format specifications behave as if a zero value or null string, as
- appropriate, had been supplied. The return value is zero on
- success, non-zero if an invalid option is supplied or a write or
- assignment error occurs.
+ appropriate, had been supplied. If FORMAT is reused, a numbered
+ argument specifier â%N$â refers to the Nth argument following the
+ highest numbered argument consumed by the previous use of FORMAT.
+
+ The return value is zero on success, non-zero if an invalid option
+ is supplied or a write or assignment error occurs.
âreadâ
read [-Eers] [-a ANAME] [-d DELIM] [-i TEXT] [-n NCHARS]
@@ -7310,6 +7323,9 @@ end of the array, and an index of -1 references the last element.
The â+=â operator appends to an array variable when assigning using
the compound assignment syntax; see *note Shell Parameters:: above.
+ If one of the word expansions in a compound array assignment unsets
+the variable, the results are unspecified.
+
An array element is referenced using â${NAME[SUBSCRIPT]}â. The
braces are required to avoid conflicts with the shell's filename
expansion operators. If the SUBSCRIPT is â@â or â*â, the word expands
@@ -7569,8 +7585,8 @@ can appear in the prompt variables âPS0â, âPS1â, âPS2â, and âPS4
â\\â
A backslash.
â\[â
- Begin a sequence of non-printing characters. Thiss could be used
- to embed a terminal control sequence into the prompt.
+ Begin a sequence of non-printing characters. This could be used to
+ embed a terminal control sequence into the prompt.
â\]â
End a sequence of non-printing characters.
@@ -12950,8 +12966,8 @@ D.1 Index of Shell Builtin Commands
(line 71)
* pwd: Bourne Shell Builtins.
(line 265)
-* read: Bash Builtins. (line 558)
-* readarray: Bash Builtins. (line 669)
+* read: Bash Builtins. (line 571)
+* readarray: Bash Builtins. (line 682)
* readonly: Bourne Shell Builtins.
(line 277)
* return: Bourne Shell Builtins.
@@ -12960,7 +12976,7 @@ D.1 Index of Shell Builtin Commands
* shift: Bourne Shell Builtins.
(line 327)
* shopt: The Shopt Builtin. (line 9)
-* source: Bash Builtins. (line 678)
+* source: Bash Builtins. (line 691)
* suspend: Job Control Builtins.
(line 141)
* test: Bourne Shell Builtins.
@@ -12971,12 +12987,12 @@ D.1 Index of Shell Builtin Commands
(line 446)
* true: Bourne Shell Builtins.
(line 512)
-* type: Bash Builtins. (line 683)
-* typeset: Bash Builtins. (line 720)
-* ulimit: Bash Builtins. (line 726)
+* type: Bash Builtins. (line 696)
+* typeset: Bash Builtins. (line 733)
+* ulimit: Bash Builtins. (line 739)
* umask: Bourne Shell Builtins.
(line 517)
-* unalias: Bash Builtins. (line 834)
+* unalias: Bash Builtins. (line 847)
* unset: Bourne Shell Builtins.
(line 535)
* wait: Job Control Builtins.
@@ -13660,138 +13676,138 @@ D.5 Concept Index
Tag Table:
-Node: Top903
-Node: Introduction2846
-Node: What is Bash?3059
-Node: What is a shell?4192
-Node: Definitions6802
-Node: Basic Shell Features10129
-Node: Shell Syntax11353
-Node: Shell Operation12380
-Node: Quoting13671
-Node: Escape Character15009
-Node: Single Quotes15544
-Node: Double Quotes15893
-Node: ANSI-C Quoting17238
-Node: Locale Translation18632
-Node: Creating Internationalized Scripts20035
-Node: Comments24233
-Node: Shell Commands25000
-Node: Reserved Words25939
-Node: Simple Commands27082
-Node: Pipelines27744
-Node: Lists31000
-Node: Compound Commands32920
-Node: Looping Constructs33929
-Node: Conditional Constructs36478
-Node: Command Grouping51615
-Node: Coprocesses53107
-Node: GNU Parallel55793
-Node: Shell Functions56711
-Node: Shell Parameters65159
-Node: Positional Parameters70060
-Node: Special Parameters71150
-Node: Shell Expansions74611
-Node: Brace Expansion76800
-Node: Tilde Expansion80136
-Node: Shell Parameter Expansion83091
-Node: Command Substitution103738
-Node: Arithmetic Expansion107267
-Node: Process Substitution108443
-Node: Word Splitting109551
-Node: Filename Expansion111995
-Node: Pattern Matching115219
-Node: Quote Removal120985
-Node: Redirections121289
-Node: Executing Commands131545
-Node: Simple Command Expansion132212
-Node: Command Search and Execution134320
-Node: Command Execution Environment136764
-Node: Environment140212
-Node: Exit Status142115
-Node: Signals144174
-Node: Shell Scripts149122
-Node: Shell Builtin Commands152420
-Node: Bourne Shell Builtins154761
-Node: Bash Builtins181480
-Node: Modifying Shell Behavior218404
-Node: The Set Builtin218746
-Node: The Shopt Builtin230740
-Node: Special Builtins247793
-Node: Shell Variables248782
-Node: Bourne Shell Variables249216
-Node: Bash Variables251724
-Node: Bash Features291008
-Node: Invoking Bash292022
-Node: Bash Startup Files298606
-Node: Interactive Shells303848
-Node: What is an Interactive Shell?304256
-Node: Is this Shell Interactive?304918
-Node: Interactive Shell Behavior305742
-Node: Bash Conditional Expressions309503
-Node: Shell Arithmetic314920
-Node: Aliases318247
-Node: Arrays321381
-Node: The Directory Stack328969
-Node: Directory Stack Builtins329766
-Node: Controlling the Prompt334211
-Node: The Restricted Shell337096
-Node: Bash POSIX Mode339978
-Node: Shell Compatibility Mode358925
-Node: Job Control367932
-Node: Job Control Basics368389
-Node: Job Control Builtins374757
-Node: Job Control Variables381545
-Node: Command Line Editing382776
-Node: Introduction and Notation384479
-Node: Readline Interaction386831
-Node: Readline Bare Essentials388019
-Node: Readline Movement Commands389827
-Node: Readline Killing Commands390823
-Node: Readline Arguments392846
-Node: Searching393936
-Node: Readline Init File396179
-Node: Readline Init File Syntax397482
-Node: Conditional Init Constructs424433
-Node: Sample Init File428818
-Node: Bindable Readline Commands431938
-Node: Commands For Moving433476
-Node: Commands For History435940
-Node: Commands For Text441331
-Node: Commands For Killing445456
-Node: Numeric Arguments448244
-Node: Commands For Completion449396
-Node: Keyboard Macros455092
-Node: Miscellaneous Commands455793
-Node: Readline vi Mode462360
-Node: Programmable Completion463337
-Node: Programmable Completion Builtins473073
-Node: A Programmable Completion Example484810
-Node: Using History Interactively490155
-Node: Bash History Facilities490836
-Node: Bash History Builtins494571
-Node: History Interaction501042
-Node: Event Designators505992
-Node: Word Designators507570
-Node: Modifiers509962
-Node: Installing Bash511899
-Node: Basic Installation513015
-Node: Compilers and Options516891
-Node: Compiling For Multiple Architectures517641
-Node: Installation Names519394
-Node: Specifying the System Type521628
-Node: Sharing Defaults522374
-Node: Operation Controls523088
-Node: Optional Features524107
-Node: Reporting Bugs536830
-Node: Major Differences From The Bourne Shell538187
-Node: GNU Free Documentation License559614
-Node: Indexes584791
-Node: Builtin Index585242
-Node: Reserved Word Index592340
-Node: Variable Index594785
-Node: Function Index612198
-Node: Concept Index626193
+Node: Top899
+Node: Introduction2838
+Node: What is Bash?3051
+Node: What is a shell?4184
+Node: Definitions6794
+Node: Basic Shell Features10121
+Node: Shell Syntax11345
+Node: Shell Operation12372
+Node: Quoting13663
+Node: Escape Character15001
+Node: Single Quotes15536
+Node: Double Quotes15885
+Node: ANSI-C Quoting17230
+Node: Locale Translation18624
+Node: Creating Internationalized Scripts20027
+Node: Comments24225
+Node: Shell Commands24992
+Node: Reserved Words25931
+Node: Simple Commands27074
+Node: Pipelines27736
+Node: Lists30992
+Node: Compound Commands32912
+Node: Looping Constructs33921
+Node: Conditional Constructs36470
+Node: Command Grouping51607
+Node: Coprocesses53099
+Node: GNU Parallel55785
+Node: Shell Functions56703
+Node: Shell Parameters65151
+Node: Positional Parameters70052
+Node: Special Parameters71142
+Node: Shell Expansions74603
+Node: Brace Expansion76792
+Node: Tilde Expansion80128
+Node: Shell Parameter Expansion83083
+Node: Command Substitution103730
+Node: Arithmetic Expansion107259
+Node: Process Substitution108435
+Node: Word Splitting109543
+Node: Filename Expansion111987
+Node: Pattern Matching115211
+Node: Quote Removal120977
+Node: Redirections121281
+Node: Executing Commands131537
+Node: Simple Command Expansion132204
+Node: Command Search and Execution134312
+Node: Command Execution Environment136756
+Node: Environment140204
+Node: Exit Status142107
+Node: Signals144166
+Node: Shell Scripts149114
+Node: Shell Builtin Commands152412
+Node: Bourne Shell Builtins154753
+Node: Bash Builtins181472
+Node: Modifying Shell Behavior219208
+Node: The Set Builtin219550
+Node: The Shopt Builtin231544
+Node: Special Builtins248597
+Node: Shell Variables249586
+Node: Bourne Shell Variables250020
+Node: Bash Variables252528
+Node: Bash Features291812
+Node: Invoking Bash292826
+Node: Bash Startup Files299410
+Node: Interactive Shells304652
+Node: What is an Interactive Shell?305060
+Node: Is this Shell Interactive?305722
+Node: Interactive Shell Behavior306546
+Node: Bash Conditional Expressions310307
+Node: Shell Arithmetic315724
+Node: Aliases319051
+Node: Arrays322185
+Node: The Directory Stack329888
+Node: Directory Stack Builtins330685
+Node: Controlling the Prompt335130
+Node: The Restricted Shell338014
+Node: Bash POSIX Mode340896
+Node: Shell Compatibility Mode359843
+Node: Job Control368850
+Node: Job Control Basics369307
+Node: Job Control Builtins375675
+Node: Job Control Variables382463
+Node: Command Line Editing383694
+Node: Introduction and Notation385397
+Node: Readline Interaction387749
+Node: Readline Bare Essentials388937
+Node: Readline Movement Commands390745
+Node: Readline Killing Commands391741
+Node: Readline Arguments393764
+Node: Searching394854
+Node: Readline Init File397097
+Node: Readline Init File Syntax398400
+Node: Conditional Init Constructs425351
+Node: Sample Init File429736
+Node: Bindable Readline Commands432856
+Node: Commands For Moving434394
+Node: Commands For History436858
+Node: Commands For Text442249
+Node: Commands For Killing446374
+Node: Numeric Arguments449162
+Node: Commands For Completion450314
+Node: Keyboard Macros456010
+Node: Miscellaneous Commands456711
+Node: Readline vi Mode463278
+Node: Programmable Completion464255
+Node: Programmable Completion Builtins473991
+Node: A Programmable Completion Example485728
+Node: Using History Interactively491073
+Node: Bash History Facilities491754
+Node: Bash History Builtins495489
+Node: History Interaction501960
+Node: Event Designators506910
+Node: Word Designators508488
+Node: Modifiers510880
+Node: Installing Bash512817
+Node: Basic Installation513933
+Node: Compilers and Options517809
+Node: Compiling For Multiple Architectures518559
+Node: Installation Names520312
+Node: Specifying the System Type522546
+Node: Sharing Defaults523292
+Node: Operation Controls524006
+Node: Optional Features525025
+Node: Reporting Bugs537748
+Node: Major Differences From The Bourne Shell539105
+Node: GNU Free Documentation License560532
+Node: Indexes585709
+Node: Builtin Index586160
+Node: Reserved Word Index593258
+Node: Variable Index595703
+Node: Function Index613116
+Node: Concept Index627111
End Tag Table
diff --git a/doc/bash.pdf b/doc/bash.pdf
index 1303fb09..eb9f142a 100644
Binary files a/doc/bash.pdf and b/doc/bash.pdf differ
diff --git a/doc/bashref.aux b/doc/bashref.aux
index fa94a864..5743a0e1 100644
--- a/doc/bashref.aux
+++ b/doc/bashref.aux
@@ -158,7 +158,7 @@
@xrdef{Modifying Shell Behavior-snt}{Section@tie 4.3}
@xrdef{The Set Builtin-title}{The Set Builtin}
@xrdef{The Set Builtin-snt}{Section@tie 4.3.1}
-@xrdef{Modifying Shell Behavior-pg}{73}
+@xrdef{Modifying Shell Behavior-pg}{74}
@xrdef{The Set Builtin-pg}{74}
@xrdef{The Shopt Builtin-title}{The Shopt Builtin}
@xrdef{The Shopt Builtin-snt}{Section@tie 4.3.2}
diff --git a/doc/bashref.bt b/doc/bashref.bt
index 46dc14dc..054755d9 100644
--- a/doc/bashref.bt
+++ b/doc/bashref.bt
@@ -35,13 +35,13 @@
\entry{logout}{68}{\code {logout}}
\entry{mapfile}{68}{\code {mapfile}}
\entry{printf}{68}{\code {printf}}
-\entry{read}{69}{\code {read}}
+\entry{read}{70}{\code {read}}
\entry{readarray}{71}{\code {readarray}}
-\entry{source}{71}{\code {source}}
-\entry{type}{71}{\code {type}}
+\entry{source}{72}{\code {source}}
+\entry{type}{72}{\code {type}}
\entry{typeset}{72}{\code {typeset}}
\entry{ulimit}{72}{\code {ulimit}}
-\entry{unalias}{73}{\code {unalias}}
+\entry{unalias}{74}{\code {unalias}}
\entry{set}{74}{\code {set}}
\entry{shopt}{78}{\code {shopt}}
\entry{dirs}{112}{\code {dirs}}
diff --git a/doc/bashref.bts b/doc/bashref.bts
index 40640e63..90717177 100644
--- a/doc/bashref.bts
+++ b/doc/bashref.bts
@@ -56,7 +56,7 @@
\entry{\code {pushd}}{113}
\entry{\code {pwd}}{56}
\initial {R}
-\entry{\code {read}}{69}
+\entry{\code {read}}{70}
\entry{\code {readarray}}{71}
\entry{\code {readonly}}{56}
\entry{\code {return}}{57}
@@ -64,19 +64,19 @@
\entry{\code {set}}{74}
\entry{\code {shift}}{57}
\entry{\code {shopt}}{78}
-\entry{\code {source}}{71}
+\entry{\code {source}}{72}
\entry{\code {suspend}}{128}
\initial {T}
\entry{\code {test}}{57}
\entry{\code {times}}{59}
\entry{\code {trap}}{59}
\entry{\code {true}}{60}
-\entry{\code {type}}{71}
+\entry{\code {type}}{72}
\entry{\code {typeset}}{72}
\initial {U}
\entry{\code {ulimit}}{72}
\entry{\code {umask}}{60}
-\entry{\code {unalias}}{73}
+\entry{\code {unalias}}{74}
\entry{\code {unset}}{61}
\initial {W}
\entry{\code {wait}}{128}
diff --git a/doc/bashref.cp b/doc/bashref.cp
index 22fea2e1..80005c3b 100644
--- a/doc/bashref.cp
+++ b/doc/bashref.cp
@@ -98,7 +98,7 @@
\entry{prompting}{114}{prompting}
\entry{restricted shell}{115}{restricted shell}
\entry{POSIX description}{116}{POSIX description}
-\entry{POSIX Mode}{116}{POSIX Mode}
+\entry{POSIX Mode}{117}{POSIX Mode}
\entry{Compatibility Level}{121}{Compatibility Level}
\entry{Compatibility Mode}{121}{Compatibility Mode}
\entry{job control}{125}{job control}
diff --git a/doc/bashref.cps b/doc/bashref.cps
index a7b2443d..5be074b0 100644
--- a/doc/bashref.cps
+++ b/doc/bashref.cps
@@ -105,7 +105,7 @@
\entry{pipeline}{10}
\entry{POSIX}{3}
\entry{POSIX description}{116}
-\entry{POSIX Mode}{116}
+\entry{POSIX Mode}{117}
\entry{process group}{3}
\entry{process group ID}{3}
\entry{process substitution}{38}
diff --git a/doc/bashref.dvi b/doc/bashref.dvi
index 59da4f33..15bd3f7a 100644
Binary files a/doc/bashref.dvi and b/doc/bashref.dvi differ
diff --git a/doc/bashref.html b/doc/bashref.html
index 01fbff94..bde971b4 100644
--- a/doc/bashref.html
+++ b/doc/bashref.html
@@ -4,9 +4,9 @@