Как мне сдвинуть массив 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)

Как предложил Тоби Спейт в комментариях, метод удаления четвертого элемента и перенумерации остальных элементов за один шаг:

$ 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это * или @, удаляет весь массив.

John1024
источник
1
Обратите внимание, что синтаксис массива оболочки - это просто способ облегчить работу с переменными с одинаковыми именами. Самого массива нет ; на самом деле, после того, как вы напишите a=(), переменная aвсе еще не определена, пока вы не назначите один из ее индексов.
Chepner
@ John1024: Спасибо за этот ответ. Не могли бы вы расширить его, включив предлагаемый ответ для достижения желаемого результата?
Энтони Уэббер
@AnthonyWebber Конечно. Я добавил раздел к ответу, чтобы показать, как форсировать нумерацию индексов.
John1024
2
Упомяну лишь альтернативный подход (который может быть более подходящим для некоторого кода): вместо того unset numbers[4], присвоить весь массив , используя нарезку, то есть numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(я вывесил бы в ответ, но не имею времени , чтобы правильно объяснить).
Тоби Спейт
@ John1024: Ценю, что вы делаете это. И спасибо Тоби :)
Энтони Уэббер
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(не Bourne-подобная оболочка вопреки 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в текущем каталоге.

Стефан Шазелас
источник