Come faccio a spostare un array bash su un indice nel mezzo?


12
1  #!/bin/bash
2  # query2.sh
3
4  numbers=(53 8 12 9 784 69 8 7 1)
5  i=4
6
7  echo ${numbers[@]} # <--- this echoes "53 8 12 9 784 69 8 7 1" to stdout.
8  echo ${numbers[i]} # <--- this echoes "784" to stdout.
9
10 unset numbers[i]
11
12 echo ${numbers[@]} # <--- this echoes "53 8 12 9 69 8 7 1" to stdout.
13 echo ${numbers[i]} # <--- stdout is blank.

Perché, nella riga 13, lo stdout è vuoto, considerando che l'array sembra essere stato aggiornato a giudicare dallo stdout della riga 12?

E quindi, cosa devo fare per ottenere la risposta prevista, "69"?


1
Considerando il tipo di lavoro di programmazione che questa domanda implica, dovresti fare attenzione: vedi C'è qualcosa di sbagliato nel mio script o Bash è molto più lento di Python?
Wildcard il

Risposte:


21

unsetrimuove un elemento. Non rinumera gli elementi rimanenti.

Possiamo usare declare -pper vedere esattamente cosa succede a numbers:

$ unset "numbers[i]"
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

Osserva che numbersnon ha più un elemento 4.

Un altro esempio

Osservare:

$ a=()
$ a[1]="element 1"
$ a[22]="element 22"
$ declare -p a
declare -a a=([1]="element 1" [22]="element 22")

La matrice anon ha elementi da 2 a 21. Bash non richiede che gli indici della matrice siano consecutivi.

Metodo suggerito per forzare una rinumerazione degli indici

Cominciamo con l' numbersarray con l'elemento mancante 4:

$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

Se desideriamo che gli indici cambino, allora:

$ numbers=("${numbers[@]}")
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [4]="69" [5]="8" [6]="7" [7]="1")

Ora c'è un numero di elemento 4e ha valore 69.

Metodo alternativo per rimuovere un elemento e rinumerare l'array in un solo passaggio

Ancora una volta, definiamo numbers:

$ numbers=(53 8 12 9 784 69 8 7 1)

Come suggerito da Toby Speight nei commenti, un metodo per rimuovere il quarto elemento e rinumerare gli elementi rimanenti in un solo passaggio:

$ numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")
$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [4]="69" [5]="8" [6]="7" [7]="1")

Come puoi vedere, il quarto elemento è stato rimosso e tutti gli elementi rimanenti sono stati rinumerati.

${numbers[@]:0:4}array di sezioni numbers: prende i primi quattro elementi a partire dall'elemento 0.

Allo stesso modo, ${numbers[@]:5}slice array numbers: prende tutti gli elementi che iniziano con l'elemento 5 e continuano fino alla fine dell'array.

Ottenere gli indici di una matrice

I valori di un array possono essere ottenuti con ${a[@]}. Per trovare gli indici (o le chiavi ) che corrispondono a tali valori, utilizzare ${!a[@]}.

Ad esempio, considera di nuovo il nostro array numberscon l'elemento mancante 4:

$ declare -p numbers
declare -a numbers=([0]="53" [1]="8" [2]="12" [3]="9" [5]="69" [6]="8" [7]="7" [8]="1")

Per vedere quali indici sono assegnati:

$ echo "${!numbers[@]}"
0 1 2 3 5 6 7 8

Ancora una volta, 4manca dall'elenco degli indici.

Documentazione

Da man bash:

L' unsetintegrato viene utilizzato per distruggere le matrici. unset name[subscript]distrugge l'elemento dell'array all'indice subscript. I pedici negativi verso array indicizzati vengono interpretati come descritto sopra. Bisogna fare attenzione per evitare effetti collaterali indesiderati causati dall'espansione del percorso. unset name, dove si nametrova un array o unset name[subscript], dove si subscripttrova * o @, rimuove l'intero array.


1
Si noti che la sintassi dell'array di shell è in realtà solo un modo per semplificare la gestione di variabili con nomi simili. Non esiste un array stesso; infatti, dopo aver scritto a=(), la variabile aè ancora indefinita fino a quando non si assegna effettivamente a uno dei suoi indici.
Chepner,

@ John1024: grazie per questa risposta. Potresti eventualmente espanderlo per includere una risposta suggerita per raggiungere il risultato desiderato?
Anthony Webber,

@AnthonyWebber Certo. Ho aggiunto una sezione alla risposta per mostrare come forzare una rinumerazione degli indici.
Giovanni 1024

2
Solo per citare un approccio alternativo (che potrebbe adattarsi meglio ad un po 'di codice): invece di unset numbers[4]assegnare l'intero array usando lo slicing, ovvero numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(vorrei pubblicare come risposta, ma non ho tempo di spiegarlo correttamente).
Toby Speight,

@ John1024: ti ringrazio per averlo fatto. E grazie Toby :)
Anthony Webber,

5

bashgli array come in ksh, non sono realmente array, sono più come array associativi con chiavi limitate a numeri interi positivi (o cosiddetti array sparsi ). Per un guscio con le matrici reali, si può dare un'occhiata a come conchiglie rc, es, fish, yash, zsh(o anche csh/ tcshse quei proiettili hanno così tanti problemi che stanno meglio evitare).

In zsh:

a=(1 2 3 4 5)
a[3]=() # remove the 3rd element
a[1,3]=() # remove the first 3 elements
a[-1]=() # remove the last element

(Si noti che in zsh, in unset 'a[3]'realtà lo imposta sulla stringa vuota per una migliore compatibilità con ksh)

in yash:

a=(1 2 3 4 5)
array -d a 3 # remove the 3rd element
array -d a 1 2 3 # remove the first 3 elements
array -d a -1 # remove the last element

in fish(non una shell tipo Bourne contraria a bash/ zsh):

set a 1 2 3 4 5
set -e a[3] # remove the 3rd element
set -e a[1..3] # remove the first 3 elements
set -e a[-1] # remove the last element

in es(basato su rc, non simile a Bourne)

a = 1 2 3 4 5
a = $a(... 2 4 ...) # remove the 3rd element
a = $a(4 ...) # remove the first 3 elements
a = $a(... `{expr $#a - 1}) # remove the last element
# or a convoluted way that avoids forking expr:
a = $a(... <={@{*=$*(2 ...); return $#*} $a})

in kshebash

È possibile utilizzare gli array come normali array se si fa:

a=("${a[@]}")

dopo ogni operazione di eliminazione o inserimento che potrebbe aver reso l'elenco degli indici non contiguo o non iniziare da 0. Notare inoltre che ksh/ basharray inizia da 0, non da 1 (tranne che $@(in qualche modo)).

In effetti, questo metterà in ordine gli elementi e li sposterà sull'indice 0, 1, 2 ... in sequenza.

Si noti inoltre che è necessario citare il number[i]in:

unset 'number[i]'

Altrimenti, verrebbe trattato come unset numberise esistesse un file chiamato numberinella directory corrente.

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.