Configuration Information:
Machine: x86_64
OS: Linux 6.14.0-37-generic #37~24.04.1-Ubuntu
Compiler: gcc 13
uname output: Linux master04 6.14.0-37-generic #37~24.04.1-Ubuntu SMP
PREEMPT_DYNAMIC Thu Nov 20 10:25:38 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu
Bash Version: 5.3
Patch Level: 0
Release Status: release
Description:
When using ${!nameref} where the nameref variable is declared with
'declare -n', the indirect expansion correctly returns the name of
the referenced variable (as documented in the manual).
However, when adding a substring expansion suffix :offset:length,
the behavior diverges depending on whether the referenced target
is a scalar or an indexed array:
- Scalar target: ${!nameref:0:n} correctly performs indirect
expansion first (returning the referenced variable NAME), then
applies the substring operation to that name.
- Array target: ${!nameref:0:n} silently SKIPS the indirect
expansion entirely and instead behaves as if the expression were
${array[0]:0:n} (i.e., nameref is transparently resolved, the
first array element is returned, and substring is applied to it).
This inconsistency is not documented anywhere in the Bash manual.
The manual section on indirect expansion (Parameter Expansion) does
not mention any distinction based on the type of the referenced
variable.
Repeat-By:
#!/usr/local/bin/bash
arr=( "a" "b" "c" "d" )
ab="cd"
declare -n ref="arr" # nameref -> array
declare -n sref="ab" # nameref -> scalar
echo "=== Without :offset:length ==="
echo "\${!ref} = ${!ref}" # prints: arr (correct)
echo "\${!sref} = ${!sref}" # prints: ab (correct)
echo ""
echo "=== With :offset:length ==="
echo "\${!ref:0:100} = ${!ref:0:100}" # prints: a (UNEXPECTED)
echo "\${!sref:0:4} = ${!sref:0:4}" # prints: ab (correct)
# Expected output for ${!ref:0:100}:
# "arr" (indirect expansion returns "arr", then :0:100 slices it)
#
# Actual output for ${!ref:0:100}:
# "a" (indirect expansion is skipped, nameref resolves to arr,
# ${arr:0:100} = ${arr[0]:0:100} = "a")
echo ""
echo "=== Additional evidence ==="
# ${!ref} works correctly (returns "arr")
# Adding :0:100 should NOT change the indirect expansion behavior
# but it does -- only when the target is an array.
Fix:
Either:
(a) Make the behavior consistent: ${!nameref:o:l} should always
perform indirect expansion first (return the referenced name),
then apply :o:l -- regardless of target type. OR
(b) Document the current divergent behavior in the manual, explaining
that ${!nameref:o:l} skips indirect expansion when the nameref
target is an indexed array.