23

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

12 Answers 12

19

I 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.

3
  • Please make a note that this doesn't work for sparse arrays. – ImHere Dec 25 '17 at 20:38
  • @Isaac there's a solution on StackOverflow if you need to handle those. – roaima Dec 25 '17 at 22:45
  • Solved here. – ImHere Sep 10 '18 at 5:05
21

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.

2
  • This is an awesome trick! – Valentin Bajrami Dec 4 '19 at 17:50
  • doesnt work if you use BASH_ARGV elsewhere in the script with values in it – Jean-Bernard Jansen Sep 17 '20 at 23:52
20

Unconventional 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:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
    
  • and if you can use zsh:

    echo ${(Oa)array}
    
5
  • just been looking up tac, as the opposite of cat quite good to remember, THANKS! – nath Dec 25 '17 at 2:17
  • 3
    Though i like the idea of 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 ;-) – George Vasiliou Dec 26 '17 at 21:46
  • @GeorgeVasiliou Yes, that will work only if all elements are one characters (numbers, letters, punctations, ...). That's why I gave also second, more general solution. – jimmij Dec 26 '17 at 22:04
  • Thank you for the zsh example! – nhooyr Jun 4 '20 at 20:05
  • The zsh example works for me, but outputs as a string, not an array – Scott Anderson Jul 24 '20 at 10:50
11

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.

4
  • Thanks for this one, it works pretty well, though for some reason 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. – nath Dec 26 '17 at 23:15
  • 1
    @nath they're indirectly used, that's what the declare line is for. – muru Dec 27 '17 at 0:37
  • Clever, but note that declare -n seems not to work in bash versions before 4.3. – G-Man Says 'Reinstate Monica' Sep 9 '18 at 21:33
  • For reference, it's called a "nameref". "A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see Bash Builtins) to create a nameref, or a reference to another variable." -- GNU Bash Manual – boweeb Apr 3 '20 at 14:22
9

To 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
2
4

Ugly, unmaintainable, but one-liner:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
4
  • Not simpler, but shorter: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'". – ImHere Jul 3 '19 at 19:11
  • And even for sparse arrays: ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'" – ImHere Jul 3 '19 at 19:24
  • @Isaac But no longer one-liner and only ugly and unmaintainable for the sparse array version, unfortunately. (Should still be faster than pipes for small arrays, though.) – user23013 Jul 3 '19 at 19:32
  • Well, technically, it is a "one-liner"; not a one command, yes, but a "one liner" it is. I agree, yes, very ugly and a maintenance problem, but fun to play with. – ImHere Jul 3 '19 at 19:46
2

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 '')

POSIXly, to reverse the POSIX shell array ($@, made of $1, $2...):

code='set --'
n=$#
while [ "$n" -gt 0 ]; do
  code="$code \"\${$n}\""
  n=$((n - 1))
done
eval "$code"
2

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
3
  • nice one!!! THX; here the one liner to copy :-) ` array=(1 2 3 4 5 6 7); for (( i=${#array[@]}-1; i>=0; i-- ));do rev[${#rev[@]}]=${array[i]}; done; echo "${rev[@]}"` – nath Jul 2 '19 at 21:14
  • Doing rev+=( "${array[i]}" ) seems simpler. – ImHere Jul 3 '19 at 20:46
  • Six of one, half-dozen of the other. I'm not fornd of that syntax, but have no reason for it - just prejudice and preference. You do you. – Paul Hodges Jul 5 '19 at 13:40
1

Though i am not going to tell something new and i will also use tac to reverse the array, i though that would be worthing to mention bellow single line solution using bash version 4.4:

$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}" |tac)

Testing:

$ array=(1 2 3 4 5 6 10 11 12)
$ echo "${array[@]}"
1 2 3 4 5 6 10 11 12
$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}"|tac)
$ echo "${array[@]}"
12 11 10 6 5 4 3 2 1

Mind that the var name inside read is the name as the original array, so no helper array is required for temp storage.

Alternative implementation by adjusting IFS :

$ IFS=$'\n' read -d '' -a array < <(printf '%s\n' "${array[@]}"|tac);declare -p array
declare -a array=([0]="12" [1]="11" [2]="10" [3]="6" [4]="5" [5]="4" [6]="3" [7]="2" [8]="1")

PS: I think above solutions will not work in bash bellow version 4.4 due to different read bash builtin function implementation.

7
  • The IFS version works but it is also printing: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Using bash 4.4-5. You got to remove ;declare -p array at the end of the first line, then it works... – nath Dec 26 '17 at 22:59
  • 1
    @nath declare -p is just a quick way to make bash print the real array (index and contents). You don't need this declare -p command in your real script. If something goes wrong in your arrays assignments you could end up in a case that ${array[0]}="1 2 3 4 5 6 10 11 12" = all values stored in the same index - using echo you will see no difference. For a quick array printout using declare -p array will return you the real array indeces and the corresponding value in each index. – George Vasiliou Dec 26 '17 at 23:17
  • @nath By the way, the read -d'\n' method did not work for you? – George Vasiliou Dec 26 '17 at 23:18
  • read -d'\n' works fine. – nath Dec 26 '17 at 23:23
  • ahhh got you! SORRY :-) – nath Dec 26 '17 at 23:25
0

Bash

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[@]}

Result

7 6 5 4 3 2 1

Version

$ tac --version
tac (GNU coreutils) 8.28
4
  • 1
  • I appreciate this answer because it uses tac -s ' ' to separate the input according to spaces. This makes the implementation short and easy to understand. – Jasha Jun 26 at 13:18
  • That being said, this answer has the drawback that the output reverse is not an array. – Jasha Jun 26 at 13:26
  • In my environment the result seems to behave like an array if I iterate over it. Also, maybe it could become a proper array by modifying the line to surround the expression (before assignment) with parenthesis, like: reverse=($(echo "${array[@]} " | tac -s ' ')) – r_alex_hall Jun 27 at 21:54
0

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[@]}"

2
  • Or index=${#array[@]} and result[--index]=$e – Stéphane Chazelas Jun 22 at 9:05
  • Welcome to the site, and thank you for your contribution. Please note that this answer is almost the same as this answer. – AdminBee Jun 22 at 9:08
-1

you 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 -1 increment parameter:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done
2
  • 1
    Note that this doesn't reverse the array, it merely prints it out in reverse order. – roaima Dec 4 '19 at 17:46
  • Agree, my point was also to consider indices access as an alternative.. – M. Modugno Dec 4 '19 at 17:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.