Is there a simple way to reverse an array?
#!/bin/bash array=(1 2 3 4 5 6 7) echo "${array[@]}"
so I would get: 7 6 5 4 3 2 1
instead of: 1 2 3 4 5 6 7
Another unconventional approach:
#!/bin/bash array=(1 2 3 4 5 6 7) f() { array=("${BASH_ARGV[@]}"); } shopt -s extdebug f "${array[@]}" shopt -u extdebug echo "${array[@]}"
Output:
7 6 5 4 3 2 1
If extdebug
is enabled, array BASH_ARGV
contains in a function all positional parameters in reverse order.
f() ( shopt -s extdebug; echo "${BASH_ARGV[@]}" ); f {1..7}
CommentedJun 10, 2022 at 14:46array
since it was the script's name. See also my answer herein. And BASH_ARGV
in bash, Shell Variables at man7.org reads: "Setting extdebug
after the shell has started to execute a script [....] may result in inconsistent values.". Does this apply here?CommentedDec 24, 2024 at 19:05Unconventional approach (all not pure bash
):
if all elements in an array are just one characters (like in the question) you can use rev
:
echo "${array[@]}" | rev
otherwise if none of the array elements contain a new line:
printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
and if you can use zsh
:
echo ${(Oa)array}
tac
, as the opposite of cat
quite good to remember, THANKS!rev
, i need to mention that rev
will not work correctly for numbers with two digits. For example an array element of 12
using rev will be printed as 21
. Give it a try ;-)CommentedDec 26, 2017 at 21:46(Oa)
and not a 0
.CommentedOct 12, 2022 at 6:42I have answered the question as written, and this code reverses the array. (Printing the elements in reverse order without reversing the array is just a for
loop counting down from the last element to zero.) This is a standard "swap first and last" algorithm.
array=(1 2 3 4 5 6 7) min=0 max=$(( ${#array[@]} -1 )) while [[ min -lt max ]] do # Swap current first and last elements x="${array[$min]}" array[$min]="${array[$max]}" array[$max]="$x" # Move closer (( min++, max-- )) done echo "${array[@]}"
It works for arrays of odd and even length.
If you actually want the reverse in another array:
reverse() { # first argument is the array to reverse # second is the output array declare -n arr="$1" rev="$2" for i in "${arr[@]}" do rev=("$i" "${rev[@]}") done }
Then:
array=(1 2 3 4) reverse array foo echo "${foo[@]}"
Gives:
4 3 2 1
This should correctly handle cases where an array index is missing, say you had array=([1]=1 [2]=2 [4]=4)
, in which case looping from 0 to the highest index may add additional, empty, elements.
shellcheck
prints two warnings: array=(1 2 3 4)
<-- SC2034: array appears unused. Verify it or export it.
and for: echo "${foo[@]}"
<-- SC2154: foo is referenced but not assigned.
declare
line is for.declare -n
seems not to work in bash versions before 4.3.CommentedSep 9, 2018 at 21:33-n
option to the declare
or local
builtin commands (see Bash Builtins) to create a nameref, or a reference to another variable." -- GNU Bash ManualTo swap the array positions in place (even with sparse arrays)(since bash 3.0):
#!/bin/bash # Declare an sparse array to test: array=([5]=101 [6]=202 [10]=303 [11]=404 [20]=505 [21]=606 [40]=707) echo "Initial array values" declare -p array swaparray(){ local temp; temp="${array[$1]}" array[$1]="${array[$2]}" array[$2]="$temp" } ind=("${!array[@]}") # non-sparse array of indexes. min=-1; max="${#ind[@]}" # limits to one before real limits. while [[ min++ -lt max-- ]] # move closer on each loop. do swaparray "${ind[min]}" "${ind[max]}" # Exchange first and last done echo "Final Array swapped in place" declare -p array echo "Final Array values" echo "${array[@]}"
On execution:
./script Initial array values declare -a array=([5]="101" [6]="202" [10]="303" [11]="404" [20]="505" [21]="606" [40]="707") Final Array swapped in place declare -a array=([5]="707" [6]="606" [10]="505" [11]="404" [20]="303" [21]="202" [40]="101") Final Array values 707 606 505 404 303 202 101
For older bash, you need to use a loop (in bash (since 2.04)) and using $a
to avoid the trailing space:
#!/bin/bash array=(101 202 303 404 505 606 707) last=${#array[@]} a="" for (( i=last-1 ; i>=0 ; i-- ));do printf '%s%s' "$a" "${array[i]}" a=" " done echo
For bash since 2.03:
#!/bin/bash array=(101 202 303 404 505 606 707) last=${#array[@]} a="";i=0 while [[ last -ge $((i+=1)) ]]; do printf '%s%s' "$a" "${array[ last-i ]}" a=" " done echo
Also (using the bitwise negation operator) (since bash 4.2+):
#!/bin/bash array=(101 202 303 404 505 606 707) last=${#array[@]} a="" for (( i=0 ; i<last ; i++ )); do printf '%s%s' "$a" "${array[~i]}" a=" " done echo
Ugly, unmaintainable, but one-liner:
eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'"
.ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
eval eval "'rev=(\$(printf \"%q \"' '\"\${array[-'{1..${#array[@]}}']}\"' '))'"
. This is for dense, non-empty arrays. Doing the same for sparse arrays should be possible too.Pure bash solution, would work as a one-liner.
$: for (( i=${#array[@]}-1; i>=0; i-- )) > do rev[${#rev[@]}]=${array[i]} > done $: echo "${rev[@]}" 7 6 5 4 3 2 1
rev+=( "${array[i]}" )
seems simpler.To reverse an arbitrary array (which may contain any number of elements with any values):
With zsh
:
array_reversed=("${(@Oa)array}")
With bash
4.4+, given that bash
variables can't contain NUL bytes anyway, you can use GNU tac -s ''
on the elements printed as NUL delimited records:
readarray -td '' array_reversed < <( ((${#array[@]})) && printf '%s\0' "${array[@]}" | tac -s '')
Note however that bash arrays were inspired from ksh arrays instead of csh/zsh arrays, and are more like associative arrays with keys limited to positive integers (so called sparse arrays), and that method doesn't preserve the keys of the arrays. For instance, for an array like:
array=( [3]=a [12]=b [42]=c )
You get
array_reversed=( [0]=c [1]=b [2]=a )
POSIXly, to reverse the one and only POSIX shell array ($@
, made of $1
, $2
...) in place:
code='set --' n=$# while [ "$n" -gt 0 ]; do code="$code \"\${$n}\"" n=$((n - 1)) done eval "$code"
#!/bin/bash (a=(1 2 3 4 5) r=(); for e in "${a[@]}"; do r=("$e" "${r[@]}"); done; declare -p a r)
prints
declare -a a=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5") declare -a r=([0]="5" [1]="4" [2]="3" [3]="2" [4]="1")
Try this
#!/bin/bash array=(1 2 3 4 5 6) index=$((${#array[@]}-1)) for e in "${array[@]}"; do result[$((index--))]="$e" done echo "${result[@]}"
Inspired from Cyrus's answer and wiki.wooledge.org. Very quick as there are no loop and no forks! ... On not too large bunch of data!!, regarding Stéphane Chazelas's comment, if you plan to manipulate big bunch of datas, you'd better mandate specialised tools!
And here, confined into one single function.
printReverseArray () { if shopt -q extdebug; then printf "%s " "${BASH_ARGV[@]}" else shopt -s extdebug "${FUNCNAME}" "$@" shopt -u extdebug fi }
Sample run:
printReverseArray world! good Hello Hello good world! printReverseArray baz "Foo bar" Foo bar baz
reverseArray() { if shopt -q extdebug; then _ArrayToReverse=("${BASH_ARGV[@]}") else local -n _ArrayToReverse=$1 shopt -s extdebug "${FUNCNAME}" "${_ArrayToReverse[@]}" shopt -u extdebug fi }
Then
myArray=({a..d}{1,2}) echo ${myArray[@]} a1 a2 b1 b2 c1 c2 d1 d2 reverseArray myArray echo ${myArray[@]} d2 d1 c2 c1 b2 b1 a2 a1
tac
, I find my approach is more than 10 times as fast as your very quick one on array=( {1..10000} )
on my system for instance (0.07 vs 0.8 second). Remember bash is one of the slowest shells around, and doing things builtin is not always a guarantee that it will be faster, especially for large datasets (where efficiency matters most).CommentedMay 16, 2023 at 6:41array=([0]="c" [1]="b" [2]="a")
: 1000x reverseArray
=> 0.034s, while your approach: 1000x readarray...tac)
=> 4.882 seconds!!!CommentedMay 16, 2023 at 7:12a=()
(where tac
is not run) and 2.6 vs 2.3 for 1000x on a=(x)
.CommentedMay 16, 2023 at 7:22According to TIMTOWDI (There Is More Than One Way To Do It), here is my solution, reversing array a
into r
:
#!/bin/bash set -u a=(1 2 3) t=("${a[@]}") declare -p a t r=() while [ "${#t[@]}" -gt 0 ] do r+=("${t[-1]}") unset 't[-1]' done echo "${r[@]}" declare -p r
When executing with BASH "4.4.23(1)-release (x86_64-suse-linux-gnu)" I got:
+ set -u + a=(1 2 3) + t=("${a[@]}") + declare -p a t declare -a a=([0]="1" [1]="2" [2]="3") declare -a t=([0]="1" [1]="2" [2]="3") + r=() + '[' 3 -gt 0 ']' + r+=("${t[-1]}") + unset 't[-1]' + '[' 2 -gt 0 ']' + r+=("${t[-1]}") + unset 't[-1]' + '[' 1 -gt 0 ']' + r+=("${t[-1]}") + unset 't[-1]' + '[' 0 -gt 0 ']' + echo 3 2 1 3 2 1 + declare -p r declare -a r=([0]="3" [1]="2" [2]="1")
a=('*' $'a\nb' 'foo bar')
for instance or after IFS=0123456789
.CommentedNov 18, 2022 at 8:44${#t[@]}
still asks the shell to split+glob it. In bash, you need to quote expansions in list contexts (and here it's in arguments to the [
command so is a list context) unless you want split+glob. See also Security implications of forgetting to quote a variable in bash/POSIX shellsCommentedNov 18, 2022 at 9:04Following Cyrus' answer and ingydotnet's comment herein I had to remove the last element of array
in
Linux xmg 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64 GNU/Linux GNU bash, Version 5.2.15(1)-release (x86_64-pc-linux-gnu)
since it was the script's name:
reverseArrayTest.sh
#!/bin/bash array=(1 2 3 4 5 6 7) echo "length=${#array[@]}" echo "array=${array[@]}" reverseArray() { shopt -s extdebug; array=("${BASH_ARGV[@]}") unset array[-1] # remove last (i.e. script name) element unwantedArray=("${BASH_ARGV[@]}") } reverseArray "${array[@]}" echo "length=${#array[@]}" echo "array=${array[@]}" echo "unwantedLength=${#unwantedArray[@]}" echo "unwantedArray=${unwantedArray[@]}"
length=7 array=1 2 3 4 5 6 7 length=7 array=7 6 5 4 3 2 1 unwantedLength=8 unwantedArray=7 6 5 4 3 2 1 reverseArrayTest.sh
See also BASH_ARGV
in bash, Shell Variables:
When a subroutine is executed, the parameters supplied are pushed onto
BASH_ARGV
. The shell setsBASH_ARGV
only when in extended debugging mode (see the description of theextdebug
option to theshopt
builtin below).
and also:
Setting
extdebug
after the shell has started to execute a script [....] may result in inconsistent values.
For the latter see also my comment to Cyrus' answer.
array=(1 2 3 4 5 6 7) echo "${array[@]} " | tac -s ' '
Or
array=(1 2 3 4 5 6 7) reverse=$(echo "${array[@]} " | tac -s ' ') echo ${reverse[@]}
7 6 5 4 3 2 1
$ tac --version tac (GNU coreutils) 8.28
tac
was already mentioned: unix.stackexchange.com/a/412874/260978, unix.stackexchange.com/a/467924/260978, unix.stackexchange.com/a/413176/260978tac -s ' '
to separate the input according to spaces. This makes the implementation short and easy to understand.reverse
is not an array.reverse=($(echo "${array[@]} " | tac -s ' '))
CommentedJun 27, 2021 at 21:54tac
won't work robustly unless you print with null byte as separator and use -s ''
as shown in Stéphane's answer.CommentedNov 18, 2022 at 19:25You can also consider using seq
:
array=(1 2 3 4 5 6 7) for i in $(seq $((${#array[@]} - 1)) -1 0); do echo "${array[$i]}" done
In FreeBSD you can omit the -1
increment parameter:
for i in $(seq $((${#array[@]} - 1)) 0); do echo "${array[$i]}" done