Як перенести масив bash на якийсь індекс посередині?


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.

Чому, у рядку 13, stdout є порожнім, враховуючи, що масив, здається, був оновлений, судячи з виводки рядка 12?

І тому, що мені робити, щоб отримати задуману відповідь "69"?


1
З огляду на тип роботи з кодування, що має на увазі це запитання, слід попередити: див. Щось із моїм сценарієм щось не так, або Bash набагато повільніше, ніж Python?
Wildcard

Відповіді:


21

unsetвидаляє елемент. Він не переносить решту елементів.

Ми можемо використовувати, declare -pщоб точно побачити, що відбувається з 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")

Зауважте, що numbersелемент більше не має 4.

Ще один приклад

Дотримуйтесь:

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

У масиві aнемає елементів від 2 до 21. Bash не вимагає, щоб індекси масиву були послідовними.

Запропонований метод примусового перенумерування індексів

Почнемо з numbersмасиву з відсутнім елементом 4:

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

Якщо ми хотіли б змінити індекси, то:

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

Зараз існує номер елемента 4і він має значення 69.

Альтернативний метод видалення елемента та масиву перенумерації за один крок

Знову визначимо numbers:

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

Як запропонував Toby Speight у коментарях, спосіб видалити четвертий елемент і перенумерувати всі інші елементи за один крок:

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

Як бачимо, четвертий елемент було видалено, а всі інші елементи були перенумеровані.

${numbers[@]:0:4}масив фрагментів numbers: він займає перші чотири елементи, починаючи з елемента 0.

Аналогічно, ${numbers[@]:5}масив фрагментів numbers: він приймає всі елементи, починаючи з елемента 5 і продовжуючи до кінця масиву.

Отримання індексів масиву

Ці значення масиву можуть бути отримані з ${a[@]}. Щоб знайти індекси (або ключі ), які відповідають цим значенням, використовуйте ${!a[@]}.

Наприклад, знову розглянемо наш масив numbersз відсутнім елементом 4:

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

Щоб побачити, яким індексам призначено:

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

Знову 4відсутній у списку індексів.

Документація

Від man bash:

unsetВбудована команда використовується для знищення масивів. unset name[subscript]знищує елемент масиву в індексі subscript. Негативні підписки на індексовані масиви інтерпретуються, як описано вище. Необхідно бути обережним, щоб уникнути небажаних побічних ефектів, викликаних розширенням імені. unset name, де nameмасив, або unset name[subscript], де subscriptє * або @, видаляє весь масив.


1
Зауважте, що синтаксис масиву оболонок - це дійсно лише спосіб полегшити роботу з аналогічними змінними. Сам масив відсутній ; насправді після написання a=()змінна aзалишається невизначеною, поки ви фактично не призначите один із її індексів.
чепнер

@ John1024: Дякую за цю відповідь. Чи можете ви розширити його, щоб включити запропоновану відповідь для досягнення наміченого результату?
Ентоні Веббер

@AnthonyWebber Звичайно. Я додав розділ до відповіді, щоб показати, як примусити перенумерування індексів.
John1024

2
Відзначимо лише альтернативний підхід (який може бути кращим для якогось коду): замість цього unset numbers[4]призначте весь масив, використовуючи нарізки, тобто numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(я б розмістив як відповідь, але не маю часу правильно пояснити).
Toby Speight

@ John1024: Дякую, що ви робите це. І thnx Toby :)
Ентоні Веббер

5

bashмасиви, такі як in ksh, насправді не є масивами, вони більше схожі на асоціативні масиви з ключами, обмеженими позитивними цілими числами (або так званими розрідженими масивами ). Для оболонки з реальними масивами, ви можете подивитися на оболонках , як rc, es, fish, yash, zsh(або навіть csh/ tcshякщо ці оболонки мають так багато проблем , вони краще уникати).

В 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

(Зауважте, що в zsh unset 'a[3]'насправді встановлюється порожній рядок для поліпшення сумісності з ksh)

в 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

в fish(не оболонці Борна всупереч 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

в es(заснований на rc, а не на Борна)

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

в kshіbash

Ви можете використовувати масиви як звичайні масиви, якщо:

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

після кожної операції видалення або вставки, яка, можливо, зробила перелік індексів не суміжним або не починається з 0. Також зауважте, що ksh/ bashмасиви починаються з 0, а не з 1 (за винятком $@(за певним чином)).

Це фактично охайне елементи та перемістить їх до індексу 0, 1, 2 ... послідовно.

Також зауважте, що вам потрібно процитувати number[i]в:

unset 'number[i]'

В іншому випадку це трактується так, як у поточному каталозі unset numberiбув файл, який називається numberi.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.