How to reverse array in bash onliner FOR loop?
BashBash Problem Overview
How can I reverse the order in which I perform a for loop for a defined array
To iterate through the array I am doing this:
$ export MYARRAY=("one" "two" "three" "four")
$ for i in ${MYARRAY[@]}; do echo $i;done
one
two
three
four
Is there a function where I can reverse the order of the array?
One thought I had is to generate a sequence of inverted indexes and call the elements by using this reversed index but maybe there is a quicker alternative, or at least easier to read.
Bash Solutions
Solution 1 - Bash
You can use the C-style for loop:
for (( idx=${#MYARRAY[@]}-1 ; idx>=0 ; idx-- )) ; do
echo "${MYARRAY[idx]}"
done
For an array with "holes", the number of elements ${#arr[@]}
doesn't correspond to the index of the last element. You can create another array of indices and walk it backwards in the same way:
#! /bin/bash
arr[2]=a
arr[7]=b
echo ${#arr[@]} # only 2!!
indices=( ${!arr[@]} )
for ((i=${#indices[@]} - 1; i >= 0; i--)) ; do
echo "${arr[indices[i]]}"
done
Solution 2 - Bash
You can use tac, which is an opposite of cat
in sense that it reverses the lines.
MYARRAY=("one" "two" "three" "four")
for item in "$MYARRAY"; do
echo "$item";
done | tac
# four
# three
# two
# one
Solution 3 - Bash
_arr+=( '"${_arrev} is an actual "${array[@]}"' ) ⏎
_arr+=( '"${_arrev} is created as a result"' )
_arr+=( '"of reversing the key order in"' )
_arr+=( '"this "${_arr}. It handles zsh and"' )
_arr+=( '"bash arrays intelligently by tracking"' )
_arr+=( '"shell "$ENV." quotes=fine ( i hope ) "' )
. <<REVERSE /dev/stdin ⏎
_arrev=( $(: $((l=${#_arr[@]}${ZSH_VERSION++1})) ; printf '"${_arr[$(('$l'-%d))]}" ' `seq 1 $l`) )
REVERSE
echo ; printf %s\\n ${_arrev}
"shell "$ENV." quotes=fine ( i hope ) "
"bash arrays intelligently by tracking"
"this "${_arr}. It handles zsh and"
"of reversing the key order in"
"${_arrev} is created as a result"
"${_arrev} is an actual "${array[@]}"
This should handle any possible array, I think.
If you're interested in what's going on up there, I suggest you have a look here first. Then maybe here, definitely here, and, if you've got the time, here and here.
In all of those answers I discuss different
aspects of the here-document (and in many others) which you can use to your advantage. For instance I discuss twice-evaluating variables, which is done above, and in one declare a function that globally declares another function named "_$1"
in just 5 or 6 lines - most of which were _$1() { func body ; }
. It's pretty handy if you use it correctly.
Regarding the auto-switch between bash/zsh,
well that's something else, but very simple as well. See here.
Solution 4 - Bash
How about this:
for i in `printf '%s\n' "${MYARRAY[@]}"|tac`; do echo $i; done
The output is:
four
three
two
one
Limitation: doesn't work if array contain newlines. As a workaround you may implement function:
reverse(){ reversed=();local i;for ((i=$#;i>0;i--)); do reversed+=("${!i}");done; }
and use it this way:
reverse "${MYARRAY[@]}" && for i in "${reversed[@]}"; do echo $i; done
Solution 5 - Bash
Simple as a string:
% unset c; a="1 2 3 4 5"; for b in $a; do c="$b $c"; done; echo $c
5 4 3 2 1
You sure you want array syntax??:
% unset c; declare -a c; a=(1 2 3 4 5); i=0; for b in ${a[*]}; \
do c[$((${#a[@]}-$i))]=$b; i=$(($i+1)); done; echo ${c[*]}
5 4 3 2 1
Solution 6 - Bash
you can also consider using seq
MYARRAY=("one" "two" "three" "four")
for i in $(seq $((${#MYARRAY[@]} - 1)) -1 0); do
echo ${MYARRAY[$i]}
done
in freebsd you can omit -1 increment parameter:
for i in $(seq $((${#MYARRAY[@]} - 1)) 0); do
echo ${MYARRAY[$i]}
done
Solution 7 - Bash
If you are talking about a sequential numerical array (say to delete bash history) you can just list this in reverse order for instance:
for i in {16..10}; do history -d $i; done
I realize that this is kind of off topic and I apologize for that however I figured that it was probably worth mentioning.
Solution 8 - Bash
I'd advise limiting one-liner usage for such things and instead write a function that's sourced into your shell or script. Here's an example for newer versions of Bash where it's possible to pass multiple arrays by reference to a function...
reverse_array(){
local -n _source_array_ref="${1}"
local -n _destination_array_ref="${2}"
for ((_index=${#_source_array_ref[@]}-1; _index>=0; _index--)); do
_destination_array_ref+=("${_source_array_ref[$_index]}")
done
}
_list=(spam ham space)
_new_list=()
reverse_array '_list' '_new_list'
printf '%s\n' "${_new_list[@]}"
#> space
#> ham
#> spam
... however, with this technique do be aware that it makes a function that's impure (has side-effects), meaning that debugging _list
or _new_list
can become difficult when in a Bash scripting mindset... also current examples don't take into account that one could re-run reverse_array
and end up with _new_list
appended to multiple times; which may or may not be desired.