Bash - inverser un tableau


16

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

Réponses:


15

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 forboucle à rebours 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.


Veuillez noter que cela ne fonctionne pas pour les tableaux clairsemés.
— Isaac

@Isaac, il existe une solution sur StackOverflow si vous avez besoin de les gérer.
— roaima

Résolu ici .
— Isaac

18

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 extdebugest activé, le tableau BASH_ARGVcontient dans une fonction tous les paramètres positionnels dans l'ordre inverse.


C'est un truc génial!
— Valentin Bajrami

15

Approche non conventionnelle (toutes non pures bash):

  • si tous les éléments d'un tableau ne sont qu'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}

vient de chercher tac, comme le contraire de cattrès bon à retenir, MERCI!
— nath

3
Bien que j'aime l'idée de rev, je dois mentionner que revcela ne fonctionnera pas correctement pour les numéros à deux chiffres. Par exemple, un élément de tableau 12 utilisant rev sera imprimé sous la forme 21. Essayez-le ;-)
— George Vasiliou

@GeorgeVasiliou Oui, cela ne fonctionnera que si tous les éléments sont constitués d'un seul caractère (chiffres, lettres, ponctuations, ...). C'est pourquoi j'ai également donné une deuxième solution, plus générale.
— jimmij

8

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, par exemple array=([1]=1 [2]=2 [4]=4), auquel cas une boucle de 0 à l'index le plus élevé peut ajouter des éléments vides supplémentaires.


Merci pour celui - ci, il fonctionne assez bien, mais pour une raison quelconque shellcheckimprime deux avertissements: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.et:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
— nath

1
@si elles sont indirectement utilisées, c'est à cela que sert la declareligne.
— muru

Intelligent, mais notez que cela declare -nne semble pas fonctionner dans les versions bash antérieures à 4.3.
— G-Man dit `` Réintègre Monica '' le

8

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 bash plus anciens, vous devez utiliser une boucle (dans bash (depuis 2.04)) et utiliser $apour éviter l'espace de fin:

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

L'adressage des éléments d'un tableau de la fin vers l'arrière avec des indices négatifs ne semble pas fonctionner dans les versions bash antérieures à 4.3.
— G-Man dit `` Réintègre Monica '' le

1
En fait, l'adressage des nombres négatifs a été modifié en 4.2-alpha. Et le script avec des valeurs négatives fonctionne à partir de cette version. @ G-Man p. Les indices négatifs des tableaux indexés, désormais traités comme des décalages par rapport à l'index maximal attribué + 1. mais Bash-hackers signale de manière incorrecte 4.1 les tableaux indexés numériquement sont accessibles depuis la fin en utilisant des index négatifs
— Isaac

3

Moche, impossible à entretenir, mais à une ligne:

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

Pas plus simple, mais plus courte: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
— Isaac

Et même pour les tableaux clairsemés:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
— Isaac

@Isaac Mais plus un-liner et seulement moche et impossible à entretenir pour la version à réseau clairsemé, malheureusement. (
— Cela devrait quand

Eh bien, techniquement, c'est un "one-liner"; pas une commande, oui, mais une "ligne unique". Je suis d'accord, oui, très moche et un problème de maintenance, mais amusant de jouer avec.
— Isaac

1

Bien que je ne vais pas dire quelque chose de nouveau et que j'utiliserai également tacpour inverser le tableau, je pense que cela vaudrait 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 la bashversion ci-dessous en 4.4raison de la readmise en œuvre de la fonction intégrée bash différente .


La IFSversion fonctionne , mais il est aussi l' impression: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Utilisation de bash 4.4-5. Vous devez retirer ;declare -p arrayà la fin de la première ligne, alors ça marche ...
— nath

1
@nath declare -pn'est qu'un moyen rapide de faire en sorte que bash imprime le vrai tableau (index et contenu). Vous n'avez pas besoin de cette declare -pcommande dans votre vrai script. Si quelque chose ne va pas dans vos affectations de tableaux, vous pourriez vous retrouver dans un cas qui ${array[0]}="1 2 3 4 5 6 10 11 12"= toutes les valeurs stockées dans le même index - en utilisant l'écho, vous ne verrez aucune différence. Pour une impression rapide du tableau à l'aide de declare -p array, vous retournerez les véritables indecs du tableau et la valeur correspondante dans chaque index.
— George Vasiliou

@nath Au fait, la read -d'\n'méthode n'a pas fonctionné pour vous?
— George Vasiliou

read -d'\n'fonctionne bien.
— nath

ahhh vous a! DÉSOLÉ :-)
— nath

1

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 bash4.4+, étant donné que les bashvariables 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 '')

POSIX, pour inverser le tableau shell POSIX ( $@, composé de $1, $2...):

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

1

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

joli!!! THX; ici le liner à copier :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); do rev [$ {# rev [@]}] = $ {array [i]}; terminé; echo "$ {rev [@]}" `
— nath

Faire rev+=( "${array[i]}" )semble plus simple.
— Isaac

Six d'un, une demi-douzaine de l'autre. Je ne suis pas étranger à cette syntaxe, mais je n'ai aucune raison pour cela - juste des préjugés et des préférences. C'est toi.
— Paul Hodges

-1

vous pouvez également envisager d'utiliser seq

array=(1 2 3 4 5 6 7)

for i in $(seq $((${#array[@]} - 1)) -1 0); do
    echo ${array[$i]}
done

dans freebsd, vous pouvez omettre le paramètre d'incrémentation -1:

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done

Notez que cela n'inverse pas le tableau, il l'imprime simplement dans l'ordre inverse.
— roaima

D'accord, je voulais aussi considérer l'accès aux indices comme une alternative ..
— M. Modugno

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.