web-dev-qa-db-fra.com

Bash - inverser un tableau

Existe-t-il un moyen simple d'inverser un tableau?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

donc j'obtiendrais: 7 6 5 4 3 2 1
au lieu de: 1 2 3 4 5 6 7

18
nath

J'ai répondu à la question telle qu'elle est écrite, et ce code inverse le tableau. (L'impression des éléments dans l'ordre inverse sans inverser le tableau est juste une boucle for décomptant du dernier élément à zéro.) Il s'agit d'un algorithme standard de "permutation du premier et du dernier".

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

Cela fonctionne pour les tableaux de longueur paire et impaire.

15
roaima

Une autre approche non conventionnelle:

#!/bin/bash

array=(1 2 3 4 5 6 7)

f() { array=("${BASH_ARGV[@]}"); }

shopt -s extdebug
f "${array[@]}"
shopt -u extdebug

echo "${array[@]}"

Production:

 7 6 5 4 3 2 1 

Si extdebug est activé, le tableau BASH_ARGV contient dans une fonction tous les paramètres positionnels dans l'ordre inverse.

18
Cyrus

Approche non conventionnelle (toutes non pures bash):

  • si tous les éléments d'un tableau sont composés d'un seul caractère (comme dans la question), vous pouvez utiliser rev:

    echo "${array[@]}" | rev
    
  • autrement:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
    
  • et si vous pouvez utiliser zsh:

    echo ${(Oa)array}
    
16
jimmij

Pour échanger les positions de tableau en place (même avec des tableaux clairsemés) (depuis 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[@]}"

À l'exécution:

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

Pour les anciens bash, vous devez utiliser une boucle (en bash (depuis 2.04)) et utiliser $a pour éviter l'espace de fuite:

#!/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

Pour bash depuis 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

Aussi (en utilisant l'opérateur de négation au niveau du bit) (depuis 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
8
Isaac

Si vous voulez réellement l'inverse dans un autre tableau:

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
}

Alors:

array=(1 2 3 4)
reverse array foo
echo "${foo[@]}"

Donne:

4 3 2 1

Cela devrait gérer correctement les cas où un index de tableau est manquant, disons que vous aviez array=([1]=1 [2]=2 [4]=4), auquel cas une boucle de 0 à l'index le plus élevé peut ajouter des éléments supplémentaires vides.

8
muru

Moche, impossible à entretenir, mais à une ligne:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"
3
user23013

La solution Pure Bash fonctionnerait comme une doublure.

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1
2
Paul Hodges

Pour inverser un tableau arbitraire (qui peut contenir n'importe quel nombre d'éléments avec n'importe quelle valeur):

Avec zsh:

array_reversed=("${(@Oa)array}")

Avec bash 4.4+, étant donné que les variables bash ne peuvent de toute façon pas contenir d'octets NUL, vous pouvez utiliser GNU tac -s '' sur les éléments imprimés en tant qu'enregistrements délimités NUL:

readarray -td '' array_reversed < <(
  ((${#array[@]})) && printf '%s\0' "${array[@]}" | tac -s '')

POSIXly, pour inverser le tableau Shell POSIX ($@, fait de $1, $2...):

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

Bien que je ne vais pas dire quelque chose de nouveau et que j'utiliserai également tac pour inverser le tableau, je pense que cela vaut la peine de mentionner une solution à une seule ligne en utilisant la version 4.4 de bash:

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

Essai:

$ 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

Gardez à l'esprit que le nom var à l'intérieur de read est le nom du tableau d'origine, donc aucun tableau d'assistance n'est requis pour le stockage temporaire.

Mise en œuvre alternative en ajustant 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: Je pense que les solutions ci-dessus ne fonctionneront pas dans bash version ci-dessous 4.4 en raison de l'implémentation de la fonction intégrée bash read.

1
George Vasiliou