The expected behaviour for the following expansions is to always result
in b='0 1':
$ IFS=' ' a=(a b) b=${!a[@]} # (1)
$ IFS=' ' a=(a b) b="${!a[@]}" # (1q)
$ IFS='+' a=(a b) b=${!a[@]} # (2)
$ IFS='+' a=(a b) b="${!a[@]}" # (2q)
$ IFS='' a=(a b) b=${!a[@]} # (3)
$ IFS='' a=(a b) b="${!a[@]}" # (3q)
And that is the behaviour we get in bash 5.0; however, in bash 5.1,
there seems to have been a regression: while (1q) "quoted; space IFS",
(3q) "quoted; null IFS, no splitting", and (2) "unquoted; nonspace IFS"
still work correctly, (2q) incorrectly results in b='0+1' which we
would expect from b="${!a[*]}" or, equivalently, b=${!a[*]}.
$ declare -p BASH_VERSION
declare -- BASH_VERSION="5.3.9(1)-release"
$ IFS=' ' a=(a b) b=${!a[@]} ; declare -p b # (1)
declare -- b="0 1"
$ IFS=' ' a=(a b) b="${!a[@]}"; declare -p b # (1q)
declare -- b="0 1"
$ IFS='+' a=(a b) b=${!a[@]} ; declare -p b # (2)
declare -- b="0 1"
$ IFS='+' a=(a b) b="${!a[@]}"; declare -p b # (2q) <<<<WRONG>>>>
declare -- b="0+1"
$ IFS='' a=(a b) b=${!a[@]} ; declare -p b # (3)
declare -- b="0 1"
$ IFS='' a=(a b) b="${!a[@]}"; declare -p b # (3q)
declare -- b="0 1"
The reason why ${!a[@]} expands incorrectly when quoted as the RHS of an
assignment is that..
subst.c:10331:parameter_brace_expand():
temp = array_keys (temp1, quoted, pflags);
arrayfunc.c:1719:array_keys()
retval = string_list_pos_params (t[0], l, quoted, pflags);
..in string_list_pos_params() (subst.c:3200), the check at line 3236
takes precedence over the one at 3247 in presence of a quoted @, which
causes string_list_dollar_at() to be invoked with pflags overrode as 0
(so, without PF_ASSIGNRHS):
3236:
else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
/* We use string_list_dollar_at, but only if the string is quoted, since
that quotes the escapes if it's not, which we don't want. We could
use string_list (the old code did), but that doesn't do the right
thing if the first character of $IFS is not a space. We use
string_list_dollar_star if the string is unquoted so we make sure that
the elements of $@ are separated by the first character of $IFS for
later splitting. */
2347:
else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS))
/* XXX - param_expand uses quoted|Q_DOUBLE_QUOTES for this case, but
that quotes the escapes. We could use string_list_internal with " "
as the second argument. */
ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp 888 */
The current order of these cases does not seem logical to me; it is
probably incorrect: I would expect the PF_ASSIGNRHS case not to care
about whether the expansion is quoted, and thus also to take precedence
over the case for double quoted expansions outside of assignments.
Unless I am missing something, reordering those two cases seems to fix
the regression; so, the fix probably looks similar to something like
this:
====
diff --git a/subst.c b/subst.c
index e4dffd34..5cf87496 100644
--- a/subst.c
+++ b/subst.c
@@ -3233,6 +3233,11 @@ string_list_pos_params (int pchar, WORD_LIST *list, int
quoted, int pflags)
separator. */
ret = string_list_dollar_star (list, quoted, 0);
}
+ else if (pchar == '@' && (pflags & PF_ASSIGNRHS))
+ /* XXX - param_expand uses quoted|Q_DOUBLE_QUOTES for this case, but
+ that quotes the escapes. We could use string_list_internal with " "
+ as the second argument. */
+ ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp
888 */
else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
/* We use string_list_dollar_at, but only if the string is quoted, since
that quotes the escapes if it's not, which we don't want. We could
@@ -3244,11 +3249,6 @@ string_list_pos_params (int pchar, WORD_LIST *list, int
quoted, int pflags)
ret = string_list_dollar_at (list, quoted, 0);
else if (pchar == '@' && quoted == 0 && ifs_is_null) /* XXX */
ret = string_list_dollar_at (list, quoted, 0); /* Posix interp 888 */
- else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS))
- /* XXX - param_expand uses quoted|Q_DOUBLE_QUOTES for this case, but
- that quotes the escapes. We could use string_list_internal with " "
- as the second argument. */
- ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp
888 */
else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS) == 0 &&
ifs_is_set && ifs_is_null == 0 &&
#if defined (HANDLE_MULTIBYTE)
====
o/
emanuele6
diff --git a/subst.c b/subst.c
index e4dffd34..5cf87496 100644
--- a/subst.c
+++ b/subst.c
@@ -3233,6 +3233,11 @@ string_list_pos_params (int pchar, WORD_LIST *list, int
quoted, int pflags)
separator. */
ret = string_list_dollar_star (list, quoted, 0);
}
+ else if (pchar == '@' && (pflags & PF_ASSIGNRHS))
+ /* XXX - param_expand uses quoted|Q_DOUBLE_QUOTES for this case, but
+ that quotes the escapes. We could use string_list_internal with " "
+ as the second argument. */
+ ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp
888 */
else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
/* We use string_list_dollar_at, but only if the string is quoted, since
that quotes the escapes if it's not, which we don't want. We could
@@ -3244,11 +3249,6 @@ string_list_pos_params (int pchar, WORD_LIST *list, int
quoted, int pflags)
ret = string_list_dollar_at (list, quoted, 0);
else if (pchar == '@' && quoted == 0 && ifs_is_null) /* XXX */
ret = string_list_dollar_at (list, quoted, 0); /* Posix interp 888 */
- else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS))
- /* XXX - param_expand uses quoted|Q_DOUBLE_QUOTES for this case, but
- that quotes the escapes. We could use string_list_internal with " "
- as the second argument. */
- ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp
888 */
else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS) == 0 &&
ifs_is_set && ifs_is_null == 0 &&
#if defined (HANDLE_MULTIBYTE)