Fun to see different techniques from different people. I have also played with this interesting problem. In my environment, the following implementations seem to be the fastest.
f21b() { local -a "arr=('\${1:'{0..$((${#1}-1))}':1}')"; arr=("${arr[@]@P}"); } # Any strings f31() { local arr i=${#1} v=$1 x='arr[--i]=v%10,v/=10,i&&x'; : $((x)); } # Only numbers ([0-9]+) I compared the following implementations: # L A Walsh f1() { local x n="$1" arr; for x in 0 1 2 3 4 5 6 7 8 9; do n=${n//$x/$x }; done; arr=($n); } f2() { local i n="${#1}" arr; for ((i=0; i<n; i++)); do arr+=("${1:i:1}"); done; } # Greg Wooledge f3() { local n="$1" tmp arr i; while ((n > 0)); do tmp+=("$((n%10))"); ((n /= 10)); done; for ((i=${#tmp[@]}-1; i >= 0; i--)); do arr+=("${tmp[i]}"); done; } f4() { local n="$1" i=${#1} arr; while ((n > 0)); do arr[--i]=$((n%10)); ((n /= 10)); done; } f5() { local i n=${#1} arr; while ((i < n)); do arr[i]="${1:i:1}"; ((i++)); done; } f6() { local i n=${#1} arr; for ((i=0; i<n; i++)); do arr[i]="${1:i:1}"; done; } # Lea Gris shopt -s extglob f7() { local arr; IFS=' ' read -ra arr <<< "${1//?()/ }"; } f8() { local arr; IFS= mapfile -s1 -t -d $'\37' arr <<<"${1//?()/$'\37'}"; arr[-1]="${arr[-1]%?}"; } # Mike Jonkmans f12() { local arr; [[ "$1" =~ ${1//?/(.)} ]]; arr=( "${BASH_REMATCH[@]:1}" ); } # my experiments with brace expansions and eval f20() { local arr; eval "for i in {0..$((${#1}-1))}; do arr[i]=\${1:i:1}; done"; } f21() { local arr; eval "arr=('\${1:'{0..$((${#1}-1))}':1}')"; arr=("${arr[@]@P}"); } f21b() { local -a "arr=('\${1:'{0..$((${#1}-1))}':1}')"; arr=("${arr[@]@P}"); } f22() { local arr; eval "arr=('\"\${1:'{0..$((${#1}-1))}':1}\"')"; local -a "arr=(${arr[*]})"; } f23() { local arr; eval "arr=('\"\${1:'{0..$((${#1}-1))}':1}\"')"; eval "arr=(${arr[*]})"; } f24() { local -a "arr=('\"\${1:'{0..$((${#1}-1))}':1}\"')"; local -a "arr=(${arr[*]})"; } # my experiments with arithmetic evaluations f30() { local arr; eval "let i={1..${#1}}-1,'arr[i]=$1/10**i%10'"; } f31() { local arr i=${#1} v=$1 x='arr[--i]=v%10,v/=10,i&&x'; : $((x)); } The result is summarized in the following table: NAME ARG bash-5.0 bash-5.1 bash-dev ---- --- ---------------- ---------------- ---------------- f1 *N 37.633 usec/eval 39.822 usec/eval 39.791 usec/eval f2 *A 44.171 usec/eval 42.643 usec/eval 43.752 usec/eval f3 *N 79.760 usec/eval 77.892 usec/eval 79.842 usec/eval f4 *N 39.870 usec/eval 40.510 usec/eval 40.929 usec/eval f5 *A 42.635 usec/eval 43.349 usec/eval 43.617 usec/eval f6 *A 39.060 usec/eval 40.191 usec/eval 40.704 usec/eval f7 *B 35.110 usec/eval 30.255 usec/eval 31.550 usec/eval f8 *R 46.324 usec/eval 36.974 usec/eval 37.672 usec/eval f12 *A 30.016 usec/eval 30.566 usec/eval 31.865 usec/eval f20 *A 37.930 usec/eval 38.076 usec/eval 38.143 usec/eval f21 *A 36.189 usec/eval 34.517 usec/eval 34.967 usec/eval f21b *A 29.954 usec/eval 28.655 usec/eval 29.729 usec/eval f22 *A 39.251 usec/eval 37.458 usec/eval 38.099 usec/eval f23 *A 45.939 usec/eval 42.630 usec/eval 43.187 usec/eval f24 *A 33.615 usec/eval 31.600 usec/eval 32.905 usec/eval f30 *N 28.908 usec/eval 28.750 usec/eval 28.871 usec/eval f31 *N 23.723 usec/eval 23.659 usec/eval 24.366 usec/eval The column ARG denotes the accepted type of the argument: *N = only numbers are accepted, *A = any characters (except for NUL), *B = any non-space characters, *R = any characters except for RS. bash-dev is built with "define(relstatus, release)" (configure.ac:27). The times are measured by EPOCHREALTIME. I have run "fN 682390" for 5000 times and obtained the average time of each call. The whole measurements are repeated five times, and the minimal time from the five results is picked up for each function and bash version.