Wie verschiebe ich ein Bash-Array um einen Index in der Mitte?

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.

Warum ist in Zeile 13 das stdout leer, wenn man bedenkt, dass das Array nach dem stdout in Zeile 12 aktualisiert worden zu sein scheint?

Und was soll ich tun, um die beabsichtigte Antwort "69" zu erhalten?

Anthony Webber
quelle
1
In Anbetracht der Art der Codierungsarbeit, die diese Frage impliziert, sollten Sie eine Warnung beachten: Siehe Stimmt etwas mit meinem Skript nicht oder ist Bash viel langsamer als Python?
Wildcard

Antworten:

21

unsetentfernt ein Element. Die verbleibenden Elemente werden nicht neu nummeriert.

Wir können verwenden declare -p, um genau zu sehen, was passiert mit 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")

Beachten Sie, dass das numberskein Element mehr hat 4.

Ein anderes Beispiel

Beobachten:

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

Array ahat keine Elemente 2 bis 21. Bash erfordert nicht, dass Array-Indizes aufeinanderfolgend sind.

Vorgeschlagene Methode, um eine Umnummerierung der Indizes zu erzwingen

Beginnen wir mit dem numbersArray mit dem fehlenden Element 4:

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

Wenn wir möchten, dass sich die Indizes ändern, dann:

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

Es gibt jetzt eine Elementnummer 4und sie hat einen Wert 69.

Alternative Methode zum Entfernen eines Elements und zum Umnummerieren des Arrays in einem Schritt

Lassen Sie uns noch einmal definieren numbers:

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

Wie von Toby Speight in den Kommentaren vorgeschlagen, eine Methode zum Entfernen des vierten Elements und zum Umnummerieren der verbleibenden Elemente in einem Schritt:

$ 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")

Wie Sie sehen können, wurde das vierte Element entfernt und alle verbleibenden Elemente wurden neu nummeriert.

${numbers[@]:0:4}Slices-Array numbers: Es werden die ersten vier Elemente beginnend mit Element 0 verwendet.

In ähnlicher Weise ${numbers[@]:5}Slice-Array numbers: Es werden alle Elemente verwendet, die mit Element 5 beginnen und bis zum Ende des Arrays reichen.

Abrufen der Indizes eines Arrays

Die Werte eines Arrays können mit erhalten werden ${a[@]}. Verwenden Sie, um die Indizes (oder Schlüssel ) zu finden, die diesen Werten entsprechen ${!a[@]}.

Betrachten Sie zum Beispiel noch einmal unser Array numbersmit dem fehlenden Element 4:

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

So sehen Sie, welche Indizes zugewiesen sind:

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

Auch 4hier fehlt in der Liste der Indizes.

Dokumentation

Von man bash:

Das unseteingebaute wird verwendet, um Arrays zu zerstören. unset name[subscript]zerstört das Array-Element am Index subscript. Negative Indizes für indizierte Arrays werden wie oben beschrieben interpretiert. Es muss darauf geachtet werden, unerwünschte Nebenwirkungen durch die Erweiterung des Pfadnamens zu vermeiden. unset name, wo nameist ein Array oder unset name[subscript], wo subscriptist * oder @, entfernt das gesamte Array.

John1024
quelle
1
Beachten Sie, dass die Shell-Array-Syntax nur eine Möglichkeit ist, den Umgang mit ähnlich benannten Variablen zu vereinfachen. Es gibt kein Array selbst; Tatsächlich ist a=()die Variable nach dem Schreiben aimmer noch undefiniert, bis Sie sie tatsächlich einem ihrer Indizes zuweisen.
Chepner
@ John1024: Danke für diese Antwort. Könnten Sie es möglicherweise um eine vorgeschlagene Antwort erweitern, um das beabsichtigte Ergebnis zu erzielen?
Anthony Webber
@ AnthonyWebber Sicher. Ich habe der Antwort einen Abschnitt hinzugefügt, um zu zeigen, wie eine Umnummerierung der Indizes erzwungen werden kann.
John1024
2
Um nur einen alternativen Ansatz zu erwähnen (der für einen Code besser geeignet sein könnte): Weisen Sie stattdessen unset numbers[4]das gesamte Array mithilfe von Slicing zu, dh numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(ich würde als Antwort posten, habe aber keine Zeit, dies richtig zu erklären).
Toby Speight
@ John1024: Schätzen Sie, dass Sie das tun. Und danke Toby :)
Anthony Webber
5

bashArrays wie in kshsind keine wirklichen Arrays, sondern eher assoziative Arrays mit Schlüsseln, die auf positive ganze Zahlen (oder sogenannte Sparse-Arrays ) beschränkt sind. Für eine Schale mit echten Arrays Sie einen Blick auf Shells wie haben kann rc, es, fish, yash, zsh(oder sogar csh/ tcshobwohl diese Granaten so viele Probleme haben , sind sie besser vermieden).

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

(Beachten Sie, dass in zsh unset 'a[3]'tatsächlich eine leere Zeichenfolge festgelegt wird, um die Kompatibilität mit zu verbessern. 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(keine Bourne-ähnliche Hülle im Gegensatz zu 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(basierend auf rc, nicht Bourne-ähnlich)

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 kshundbash

Sie können die Arrays als normale Arrays verwenden, wenn Sie Folgendes tun:

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

Nach jedem Löschen oder Einfügen von Vorgängen, bei denen die Liste der Indizes möglicherweise nicht zusammenhängend ist oder nicht bei 0 beginnt. Beachten Sie auch, dass ksh/ basharrays bei 0 und nicht bei 1 beginnen (außer $@(in gewisser Weise)).

Dadurch werden die Elemente aufgeräumt und nacheinander in den Index 0, 1, 2 ... verschoben.

Beachten Sie auch, dass Sie Folgendes angeben müssen number[i]:

unset 'number[i]'

Andernfalls würde dies so behandelt, als ob im aktuellen Verzeichnis unset numberieine Datei aufgerufen numberiworden wäre.

Stéphane Chazelas
quelle