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
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.
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}
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.
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.
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
Ugly, unmaintainable, but one-liner:
eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
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"
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
#!/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[@]}"
According 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")
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
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
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 the -1 increment parameter:
for i in $(seq $((${#array[@]} - 1)) 0); do
echo "${array[$i]}"
done