How to reverse array in bash onliner FOR loop?

Bash

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpedrosaurioView Question on Stackoverflow
Solution 1 - BashchorobaView Answer on Stackoverflow
Solution 2 - BashberealView Answer on Stackoverflow
Solution 3 - BashmikeservView Answer on Stackoverflow
Solution 4 - BashAlekView Answer on Stackoverflow
Solution 5 - BashJohan SnowgooseView Answer on Stackoverflow
Solution 6 - BashM. ModugnoView Answer on Stackoverflow
Solution 7 - BashRobertView Answer on Stackoverflow
Solution 8 - BashS0AndS0View Answer on Stackoverflow