38

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

    15 Answers 15

    32

    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.

    5
    • 2
      This is an awesome trick!CommentedDec 4, 2019 at 17:50
    • doesnt work if you use BASH_ARGV elsewhere in the script with values in itCommentedSep 17, 2020 at 23:52
    • 2
      Can be simplified to f() ( shopt -s extdebug; echo "${BASH_ARGV[@]}" ); f {1..7}CommentedJun 10, 2022 at 14:46
    • @ingydotnet This can be confined into 1 (recursive) function: my two version of thisCommentedMar 9, 2023 at 21:58
    • I had to remove the last element of array 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:05
    26

    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 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} 
    7
    • 1
      just been looking up tac, as the opposite of cat quite good to remember, THANKS!
      – nath
      CommentedDec 25, 2017 at 2:17
    • 4
      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 ;-)CommentedDec 26, 2017 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
      CommentedDec 26, 2017 at 22:04
    • The zsh example works for me, but outputs as a string, not an arrayCommentedJul 24, 2020 at 10:50
    • the zsh example is great! the first time it did not work for me because I did not understand it was (Oa) and not a 0.CommentedOct 12, 2022 at 6:42
    20

    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.

    6
    • 1
      Please make a note that this doesn't work for sparse arrays.
      – user232326
      CommentedDec 25, 2017 at 20:38
    • @Isaac there's a solution on StackOverflow if you need to handle those.CommentedDec 25, 2017 at 22:45
    • 1
      Solved here.
      – user232326
      CommentedSep 10, 2018 at 5:05
    • That looks a lot (though not completely) like a bubble sort.
      – RonJohn
      CommentedJul 3, 2024 at 19:42
    • @RonJohn it's similar but not the sameCommentedJul 4, 2024 at 6:21
    16

    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
      CommentedDec 26, 2017 at 23:15
    • 1
      @nath they're indirectly used, that's what the declare line is for.
      – muru
      CommentedDec 27, 2017 at 0:37
    • 1
      Clever, but note that declare -n seems not to work in bash versions before 4.3.CommentedSep 9, 2018 at 21:33
    • 1
      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
      CommentedApr 3, 2020 at 14:22
    12

    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
    7

    Ugly, unmaintainable, but one-liner:

    eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'" 
    5
    • Not simpler, but shorter: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
      – user232326
      CommentedJul 3, 2019 at 19:11
    • And even for sparse arrays: ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
      – user232326
      CommentedJul 3, 2019 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
      CommentedJul 3, 2019 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.
      – user232326
      CommentedJul 3, 2019 at 19:46
    • Nice hack. You can even make this work safely for creating a reversed 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.
      – Socowi
      CommentedAug 18, 2021 at 10:41
    6

    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
    • 1
      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
      CommentedJul 2, 2019 at 21:14
    • 2
      Doing rev+=( "${array[i]}" ) seems simpler.
      – user232326
      CommentedJul 3, 2019 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.CommentedJul 5, 2019 at 13:40
    6

    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" 
      3
      #!/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") 
        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[@]}" 
        2
        • 1
          Or index=${#array[@]} and result[--index]=$eCommentedJun 22, 2021 at 9:05
        • 1
          Welcome to the site, and thank you for your contribution. Please note that this answer is almost the same as this answer.
          – AdminBee
          CommentedJun 22, 2021 at 9:08
        1

        Two different versions

        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.

        Print submited array in reversed order:

        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 

        Reverse array variable:

        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 
        7
        • Note that despite the fork and exec of 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:41
        • @StéphaneChazelas Correct! Answer edited! Anyway, if you plan to reverse repeatedly a small array, your solution will quickly become very slow.CommentedMay 16, 2023 at 6:58
        • Actually, I find that my approach is still faster even on an empty array. Not sure how bash can be so slow on yours that it would be slower than forking + executing + shoving data through pipes. Maybe the option processing is very slow in bash.CommentedMay 16, 2023 at 7:04
        • Just tested with array=([0]="c" [1]="b" [2]="a"): 1000x reverseArray => 0.034s, while your approach: 1000x readarray...tac) => 4.882 seconds!!!CommentedMay 16, 2023 at 7:12
        • I don't know why I get so bad perf with your approach, I'll investigate a bit more when I have some time. That's with bash 5.1 on a 13 year old i5-based PC running Ubuntu 22.04. I get 2.6 vs 0.7 seconds for 1000x on a=() (where tac is not run) and 2.6 vs 2.3 for 1000x on a=(x).CommentedMay 16, 2023 at 7:22
        0

        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") 
        9
        • 1
          Wasn't aware of that; thanks!
          – U. Windl
          CommentedNov 18, 2022 at 8:25
        • Well, thinking about it, the extra level of indirection isn't really needed. Originally I had associative arrays in mind, but reversing the keys doesn't really make any sense, so I simplified the solution.
          – U. Windl
          CommentedNov 18, 2022 at 8:31
        • You're still missing some quotes. Try it with a=('*' $'a\nb' 'foo bar') for instance or after IFS=0123456789.CommentedNov 18, 2022 at 8:44
        • Agreed, but the original question does not use such complicated items ;-) Also it's one pair of double-quotes missing, right?
          – U. Windl
          CommentedNov 18, 2022 at 8:55
        • 2
          ${#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:04
        0

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

        Output

        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 sets BASH_ARGV only when in extended debugging mode (see the description of the extdebug option to the shopt 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.

          -1

          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 
          5
          • 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
            CommentedJun 26, 2021 at 13:18
          • That being said, this answer has the drawback that the output reverse is not an array.
            – Jasha
            CommentedJun 26, 2021 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 ' '))CommentedJun 27, 2021 at 21:54
          • 2
            tac 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:25
          -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 the -1 increment parameter:

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

          You must log in to answer this question.

          Start asking to get answers

          Find the answer to your question by asking.

          Ask question

          Explore related questions

          See similar questions with these tags.