Bash - inverte un array


16

Esiste un modo semplice per invertire un array?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

quindi otterrei: 7 6 5 4 3 2 1
invece di:1 2 3 4 5 6 7

Risposte:


15

Ho risposto alla domanda come scritto e questo codice inverte l'array. (La stampa degli elementi in ordine inverso senza invertire l'array è solo un forloop che esegue il conto alla rovescia dall'ultimo elemento a zero.) Questo è un algoritmo standard di "primo e ultimo scambio".

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

Funziona con matrici di lunghezza pari e dispari.


Si noti che ciò non funziona con array sparsi.
— Isacco,

@Isaac c'è una soluzione su StackOverflow se devi gestirli.
— roaima,

Risolto qui .
— Isacco,

18

Un altro approccio non convenzionale:

#!/bin/bash

array=(1 2 3 4 5 6 7)

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

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

echo "${array[@]}"

Produzione:

7 6 5 4 3 2 1

Se extdebugabilitato, l'array BASH_ARGVcontiene in una funzione tutti i parametri posizionali in ordine inverso.


Questo è un trucco fantastico!
— Valentin Bajrami il

15

Approccio non convenzionale (tutto non puro bash):

  • se tutti gli elementi in un array sono solo uno (come nella domanda) puoi usare rev:

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

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • e se puoi usare zsh:

    echo ${(Oa)array}

ho appena guardato in alto tac, come il contrario di catabbastanza buono da ricordare, GRAZIE!
— Nath,

3
Anche se mi piace l'idea rev, devo dire che revnon funzionerà correttamente per i numeri con due cifre. Ad esempio, un elemento dell'array di 12 utilizzo rev verrà stampato come 21. Provalo ;-)
— George Vasiliou il

@GeorgeVasiliou Sì, funzionerà solo se tutti gli elementi sono composti da un carattere (numeri, lettere, punteggiatura, ...). Ecco perché ho dato anche una seconda soluzione più generale.
— jimmij,

8

Se si desidera effettivamente il contrario in un altro 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
}

Poi:

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

dà:

4 3 2 1

Questo dovrebbe gestire correttamente i casi in cui manca un indice di array, diciamo che avevi array=([1]=1 [2]=2 [4]=4), nel qual caso il looping da 0 all'indice più alto può aggiungere ulteriori elementi vuoti.


Grazie per questo, funziona abbastanza bene, anche se per qualche motivo shellcheckstampa due avvertimenti: array=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.e per:echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
— nath,

1
@nath sono usati indirettamente, ecco a cosa declareserve la linea.
— Muru,

Intelligente, ma nota che declare -nsembra non funzionare nelle versioni bash precedenti alla 4.3.
— G-Man dice "Ripristina Monica" il

8

Per scambiare le posizioni dell'array in posizione (anche con array sparsi) (da 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[@]}"

In esecuzione:

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

Per bash più vecchio, devi usare un loop (in bash (dal 2.04)) e usare $aper evitare lo spazio finale:

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

Per bash dal 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

Inoltre (usando l'operatore di negazione bit a bit) (dalla 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

Affrontare gli elementi di un array dalla fine all'indietro con sottoscrizioni negative sembra non funzionare nelle versioni bash precedenti alla 4.3.
— G-Man dice "Ripristina Monica" il

1
In realtà, indirizzare i numeri negativi è stato modificato in 4.2-alpha. E lo script con valori negati funziona da quella versione. @ G-Man p. Sottoscrizioni negative ad array indicizzati, ora trattati come offset dall'indice massimo assegnato + 1. ma i Bash-hacker riportano erroneamente 4.1 array indicizzati numericamente sono accessibili dalla fine usando indici negativi
— Isaac

3

Brutto, non mantenibile, ma univoco:

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

Non più semplice, ma più breve: eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'".
— Isaac,

E anche per matrici sparse:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
— Isaac il

@Isaac Ma non è più un problema e solo brutto e non mantenibile per la versione sparsa dell'array, sfortunatamente. (Dovrebbe essere comunque più veloce dei tubi per piccoli array).
— user23013

Bene, tecnicamente, è un "one-liner"; non è un comando, sì, ma un "one liner" lo è. Sono d'accordo, sì, molto brutto e un problema di manutenzione, ma divertente con cui giocare.
— Isaac,

1

Anche se non ho intenzione di dire qualcosa di nuovo e userò anche tacper invertire l'array, penso che varrebbe la pena menzionare la seguente soluzione a linea singola usando bash versione 4.4:

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

test:

$ 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

Ricorda che il nome var all'interno di read è il nome dell'array originale, quindi per l'archiviazione temporanea non è necessario alcun array helper.

Implementazione alternativa regolando 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: Penso che le soluzioni precedenti non funzioneranno nella bashversione qui sotto a 4.4causa della diversa readimplementazione della funzione integrata bash.


La IFSversione funziona, ma è anche la stampa: declare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12"). Usando bash 4.4-5. Devi rimuovere ;declare -p arrayalla fine della prima riga, quindi funziona ...
— Nath

1
@nath declare -pè solo un modo rapido per fare in modo che bash stampi il vero array (indice e contenuti). Non hai bisogno di questo declare -pcomando nel tuo vero script. Se qualcosa va storto nelle assegnazioni di array, potresti finire nel caso in cui ${array[0]}="1 2 3 4 5 6 10 11 12"= tutti i valori siano memorizzati nello stesso indice - usando l'eco non vedrai alcuna differenza. Per una rapida stampa dell'array, l'utilizzo declare -p arrayrestituirà gli indici reali dell'array e il valore corrispondente in ciascun indice.
— George Vasiliou,

@nath A proposito, il read -d'\n'metodo non ha funzionato per te?
— George Vasiliou,

read -d'\n'funziona bene.
— nath,

ahhh ti ho preso! SPIACENTE :-)
— nath,

1

Per invertire una matrice arbitraria (che può contenere un numero qualsiasi di elementi con qualsiasi valore):

Con zsh:

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

Con bash4.4+, dato che le bashvariabili non possono contenere comunque byte NUL, puoi usare GNU tac -s ''sugli elementi stampati come record delimitati da NUL:

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

POSIX, per invertire l'array di shell POSIX ( $@, composto da $1, $2...):

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

1

Pura soluzione di bash, funzionerebbe come una fodera.

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

Ben fatto!!! GRAZIE; qui l'unico liner da copiare :-) `array = (1 2 3 4 5 6 7); for ((i = $ {# array [@]} - 1; i> = 0; i--)); esegui rev [$ {# rev [@]}] = $ {array [i]}; fatto; echo "$ {rev [@]}" `
— nath

Fare rev+=( "${array[i]}" )sembra più semplice.
— Isaac,

Sei di uno, mezza dozzina dell'altro. Non sono provvisto di quella sintassi, ma non ho motivo per farlo - solo pregiudizio e preferenza. Tu lo fai.
— Paul Hodges,

-1

puoi anche considerare l'utilizzo seq

array=(1 2 3 4 5 6 7)

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

in freebsd puoi omettere -1 parametro di incremento:

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

Si noti che ciò non inverte l'array, ma semplicemente lo stampa in ordine inverso.
— roaima,

D'accordo, il mio punto era anche considerare l'accesso agli indici come un'alternativa.
— M. Modugno il

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.