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

nath
la source

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.

roaima
la source
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.

Cyrus
la source
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}
jimmij
la source
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.

muru
la source
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
Isaac
la source
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}']}\"'"
user23013
la source
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 .

George Vasiliou
la source
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"
Stéphane Chazelas
la source
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
Paul Hodges
la source
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
M. Modugno
la source
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