Jak przesunąć tablicę bash przy jakimś indeksie pośrodku?

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.

Dlaczego w linii 13 stdout jest puste, biorąc pod uwagę, że tablica wydaje się być zaktualizowana, sądząc po stdout linii 12?

A zatem, co powinienem zrobić, aby uzyskać zamierzoną odpowiedź „69”?

Anthony Webber
źródło
1
Biorąc pod uwagę rodzaj kodowania, jaki sugeruje to pytanie, powinieneś ostrzec: zobacz Czy coś jest nie tak z moim skryptem lub czy Bash jest znacznie wolniejszy niż Python?
Wildcard,

Odpowiedzi:

21

unsetusuwa element. Nie przenumerowuje pozostałych elementów.

Możemy użyć, declare -paby zobaczyć dokładnie, co się dzieje z 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")

Zauważ, że numbersnie ma już elementu 4.

Inny przykład

Przestrzegać:

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

Tablica anie ma elementów od 2 do 21. Bash nie wymaga, aby indeksy tablic były następujące po sobie.

Sugerowana metoda wymuszenia numeracji indeksów

Zacznijmy od numberstablicy z brakującym elementem 4:

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

Jeśli chcielibyśmy zmienić wskaźniki, to:

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

Jest teraz numer elementu 4i ma on wartość 69.

Alternatywna metoda usuwania elementu i zmiany numeracji tablicy w jednym kroku

Ponownie zdefiniujmy numbers:

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

Jak zasugerował Toby Speight w komentarzach, metoda usunięcia czwartego elementu i zmiany numeracji pozostałych elementów w jednym kroku:

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

Jak widać, czwarty element został usunięty, a wszystkie pozostałe elementy zostały ponumerowane.

${numbers[@]:0:4}tablica plasterków numbers: pobiera pierwsze cztery elementy, zaczynając od elementu 0.

Podobnie, ${numbers[@]:5}pokrój tablicę numbers: bierze wszystkie elementy, zaczynając od elementu 5 i kontynuując do końca tablicy.

Uzyskiwanie wskaźników tablicy

Te wartości tablicy można uzyskać ${a[@]}. Aby znaleźć indeksy (lub klucze ) odpowiadające tym wartościom, użyj ${!a[@]}.

Na przykład rozważmy jeszcze raz naszą tablicę numbersz brakującym elementem 4:

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

Aby zobaczyć, które indeksy są przypisane:

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

Znowu 4brakuje jej na liście indeksów.

Dokumentacja

Od man bash:

unsetWbudowane służy do niszczenia tablic. unset name[subscript]niszczy element tablicy o indeksie subscript. Ujemne indeksy dolne do indeksowanych tablic są interpretowane jak opisano powyżej. Należy zachować ostrożność, aby uniknąć niepożądanych efektów ubocznych spowodowanych rozszerzaniem nazwy ścieżki. unset name, gdzie namejest tablica lub unset name[subscript], gdzie subscriptjest * lub @, usuwa całą tablicę.

John1024
źródło
1
Zauważ, że składnia tablicy powłoki jest naprawdę tylko sposobem na ułatwienie radzenia sobie ze zmiennymi o podobnych nazwach. Nie ma samej tablicy ; w rzeczywistości po napisaniu a=()zmienna ajest nadal niezdefiniowana, dopóki nie zostanie przypisana do jednego z jej indeksów.
chepner
@ John1024: Dziękuję za tę odpowiedź. Czy mógłbyś ją rozszerzyć o sugerowaną odpowiedź na osiągnięcie zamierzonego rezultatu?
Anthony Webber,
@AnthonyWebber Sure. Do odpowiedzi dodałem sekcję, aby pokazać, jak wymusić zmianę numeracji indeksów.
John1024,
2
Wystarczy wspomnieć o alternatywnym podejściu (które może lepiej pasować do jakiegoś kodu): zamiast unset numbers[4]przypisać całą tablicę za pomocą krojenia, tj. numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(Napisałbym jako odpowiedź, ale nie mam czasu na właściwe wyjaśnienie).
Toby Speight,
@ John1024: Doceń, że to robisz. I dzięki Toby :)
Anthony Webber,
5

bashtablice jak w ksh, nie są tak naprawdę tablicami, są bardziej jak tablice asocjacyjne z kluczami ograniczonymi do dodatnich liczb całkowitych (lub tak zwanych tablic rzadkich ). Na powłoce z prawdziwymi tablicami, można przyjrzeć się jak muszle rc, es, fish, yash, zsh(lub nawet csh/tcsh chociaż te muszle mają tak wiele problemów oni lepiej unikać).

W 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

(Zauważ, że w zsh, unset 'a[3]'faktycznie ustawia go na pusty ciąg znaków dla lepszej kompatybilności zksh )

w 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

w fish(nie przypomina powłoki Bourne'a w przeciwieństwie do 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

w es(oparty na rc, nie podobny do Bourne'a)

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})

w ksh ibash

Możesz użyć tablic jako zwykłych tablic, jeśli:

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

po każdej operacji usuwania lub wstawiania, która mogła sprawić, że lista indeksów nie była ciągła lub nie zaczynała się od 0. Należy również pamiętać, że ksh/ bashtablice zaczynają się od 0, a nie 1 (z wyjątkiem$@ (pod pewnymi względami)).

To w efekcie uporządkuje elementy i przeniesie je kolejno do indeksu 0, 1, 2 ...

Pamiętaj również, że musisz podać number[i]w:

unset 'number[i]'

W przeciwnym razie byłoby to traktowane tak, unset numberijakby numberiw bieżącym katalogu był plik .

Stéphane Chazelas
źródło