Simple one line trick for dumping array.
I've added one value with spaces:
foo=([12]="bar" [42]="foo bar baz" [35]="baz")
For a quick dump of bash arrays or associative arrays I use this one line command:
paste <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
Will render:
12 bar
35 baz
42 foo bar baz
Explanation
printf "%s\n" "${!foo[@]}" will print all keys separated by a newline,
printf "%s\n" "${foo[@]}" will print all values separated by a newline,
paste <(cmd1) <(cmd2) will merge output of cmd1 and cmd2 line by line.
Tuning
This could be tuned by -d switch:
paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
Output:
12:bar
35:baz
42:foo bar baz
or even:
paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[35]='baz'
foo[42]='foo bar baz'
Associative array will work the same:
declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
paste -d = <(printf "bar[%s]\n" "${!bar[@]}") <(printf '"%s"\n' "${bar[@]}")
bar[foo bar]="Hello world!"
bar[foo]="snoopy"
bar[bar]="nice"
bar[baz]="cool"
Issue with newlines or special chars: unfortunately, there is at least one condition making this not work anymore: when variable do contains a newline:
foo[17]=$'There is one\nnewline'
Command paste will merge line-by-line, so output will become wrong:
paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[17]='There is one
foo[35]=newline'
foo[42]='baz'
='foo bar baz'
For this work, you could use %q instead of %s in second printf command (and wipe quoting):
paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "%q\n" "${foo[@]}")
Will render perfect ( and reusable! ):
foo[12]=bar
foo[17]=$'There is one\nnewline'
foo[35]=baz
foo[42]=foo\ bar\ baz
From man bash:
%q causes printf to output the corresponding argument in a
format that can be reused as shell input.
Dump array in reusable format (without loop): pure bash, without loop and without fork, using two step, you could build a variable ready to re-define your array elsewhere.
Another way of doing this by rendering a format string for printf command:
printf -v fmt '[%d]=%%q\\n' "${!foo[@]}"
printf "$fmt" "${foo[@]}"
Output:
[12]=bar
[17]=$'There is one\nnewline'
[35]=baz
[42]=foo\ bar\ baz
Note: by using printf -v you could populate a string variable that you could use to define a new variable:
printf -v fmt '[%d]=%%q\\n' "${!foo[@]}"
printf -v asString "$fmt" "${foo[@]}"
declare -a "newVar=($asString)"
declare -p newVar
declare -a newVar=([12]="bar" [17]=$'There is one\nnewline' [35]="baz" [42]="foo bar baz")
But for Associative arrays, you have to care about presence of percent sign in index names:
bar['100%var']=42
varIdx=("${!bar[@]}")
printf -v fmt '[%q]=%%q\\n' "${varIdx[@]//\%/%%}"
printf "$fmt" "${bar[@]}"
[100%var]=42
[foo]=snoopy
[bar]=nice
[baz]=cool
[foo\ bar]=Hello\ world\!
Then same, for creating a new associative array:
printf -v asString "$fmt" "${bar[@]}"
declare -A "newAssoc=($asString)"
declare -p newAssoc
declare -A newAssoc=([100%var]="42" [foo]="snoopy" [bar]="nice" [baz]="cool" ["foo bar"]="Hello world!" )
By using a function:
dumpArray() {
local -n _ary=$1
local _idx
local -i _idlen=0
for _idx in "${!_ary[@]}"; do
_idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
done
for _idx in "${!_ary[@]}"; do
printf "%-*s: %s\n" "$_idlen" "$_idx" \
"${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C }"
done
}
Then now:
dumpArray foo
12: bar
17: There is one
newline
35: baz
42: foo bar baz
dumpArray bar
foo : snoopy
bar : nice
baz : cool
foo bar: Hello world!
About UTF-8 format output
From UTF-8 string length, adding:
strU8DiffLen() { local chLen=${#1} LANG=C LC_ALL=C;return $((${#1}-chLen));}
or better, using printf -v _ '%s%n'
Then
dumpArray() {
local -n _ary=$1
local _idx
local -i _idlen=0 _idbytes
for _idx in "${!_ary[@]}"; do
_idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
done
for _idx in "${!_ary[@]}"; do
printf -v _ '%s%n' "$_idx" _idbytes
printf "%-*s: %s\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
"${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C }"
done
}
Demo:
foo=([12]="bar" [42]="foo bar baz" [35]="baz")
declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
foo[17]=$'There is one\nnewline'
LANG=fr.UTF-8 printf -v bar[déjà] $'%(%a %d %b\n%Y\n%T)T' -1
dumpArray bar
déjà : ven 24 déc
2021
08:36:05
foo : snoopy
bar : nice
baz : cool
foo bar: Hello world!
dumpArray foo
12: bar
17: There is one
newline
35: baz
42: foo bar baz
More complete function
- Align number to right, in both: keys and values
- Support for UTF-8 in keys and values
- Support multi-line in keys and values
Code:
dumpArray() {
local -n _ary=$1
local _idx _align
local -i _idlen=0 _idbytes _valLen
case ${_ary@a} in
*a*) _idlen=(${!_ary[@]})
_idlen=${#_idlen[${#_idlen[@]}-1]} ;;
*A*) for _idx in "${!_ary[@]}"; do
_idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
done
_align=- ;;
*) return 1 ;;
esac
if [[ ${_ary@a} == *i* ]]; then
read -r _valLen < <(printf '%d\n' "${_ary[@]}"|sort -rn)
_valLen=${#_valLen}
for _idx in "${!_ary[@]}"; do
printf -v _ '%s%n' "$_idx" _idbytes
printf "%${_align}*s: %*d\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
$_valLen "${_ary["$_idx"]}"
done
else
for _idx in "${!_ary[@]}"; do
printf -v _ '%s%n' "$_idx" _idbytes
printf "%${_align}*s: %s\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
"${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C }"
done
fi
}
With auto-completion:
_dumpArray() {
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=(
$(compgen -W '$(
declare |
sed -ne "s/^\([^ ]\+\)=(.*/\1/p"
)' -- "${cur-}"
)
)
}
complete -F _dumpArray dumpArray
declare -ai testIArray=([12345]=34 [12]=14141551 [42]=664446)
dumpArray testIArray
12: 14141551
42: 664446
12345: 34
See also