Bagaimana cara menggeser bash array di beberapa indeks di tengah?

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.

Mengapa, di baris 13, apakah stdout kosong, mengingat bahwa array tampaknya telah diperbarui dilihat dari stdout baris 12?

Dan karena itu, apa yang harus saya lakukan untuk mendapatkan jawaban yang dimaksud, "69"?

Anthony Webber
sumber
1
Mengingat jenis pekerjaan pengkodean menyiratkan pertanyaan ini, Anda harus menerima peringatan: lihat Apakah ada yang salah dengan skrip saya atau apakah Bash jauh lebih lambat daripada Python?
Wildcard

Jawaban:

21

unsetmenghapus elemen. Itu tidak memberi nomor baru pada elemen yang tersisa.

Kita dapat menggunakan declare -puntuk melihat apa yang terjadi pada 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")

Amati numberstidak lagi memiliki elemen 4.

Contoh lain

Mengamati:

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

Array atidak memiliki elemen 2 hingga 21. Bash tidak mengharuskan indeks array berturut-turut.

Metode yang disarankan untuk memaksa nomor baru indeks

Mari kita mulai dengan numbersarray dengan elemen yang hilang 4:

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

Jika kami ingin indeks berubah, maka:

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

Sekarang ada nomor elemen 4dan memiliki nilai 69.

Metode alternatif untuk menghapus elemen & nomor baru dalam satu langkah

Sekali lagi, mari kita definisikan numbers:

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

Seperti yang disarankan oleh Toby Speight dalam komentar, metode untuk menghapus elemen keempat dan memberi nomor baru pada elemen yang tersisa semuanya dalam satu langkah:

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

Seperti yang Anda lihat, elemen keempat telah dihapus dan semua elemen yang tersisa diberi nomor baru.

${numbers[@]:0:4}slices array numbers: dibutuhkan empat elemen pertama dimulai dengan elemen 0.

Demikian pula, ${numbers[@]:5}slice array numbers: dibutuhkan semua elemen dimulai dengan elemen 5 dan berlanjut ke akhir array.

Memperoleh indeks array

Nilai - nilai array dapat diperoleh dengan ${a[@]}. Untuk menemukan indeks (atau kunci ) yang sesuai dengan nilai-nilai itu, gunakan ${!a[@]}.

Misalnya, pertimbangkan lagi array kami numbersdengan elemen yang hilang 4:

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

Untuk melihat indeks mana yang ditugaskan:

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

Sekali lagi, 4tidak ada dalam daftar indeks.

Dokumentasi

Dari man bash:

The unsetbuiltin digunakan untuk menghancurkan array. unset name[subscript]menghancurkan elemen array pada indeks subscript. Subskrip negatif untuk array yang diindeks ditafsirkan seperti yang dijelaskan di atas. Perawatan harus diambil untuk menghindari efek samping yang tidak diinginkan yang disebabkan oleh ekspansi pathname. unset name, di mana nameadalah sebuah array, atau unset name[subscript], di mana subscriptadalah * atau @, menghapus seluruh array.

John1024
sumber
1
Perhatikan bahwa sintaks array shell benar-benar hanya cara untuk membuatnya mudah untuk berurusan dengan variabel yang bernama sama. Tidak ada array itu sendiri; pada kenyataannya, setelah Anda menulis a=(), variabel amasih tidak ditentukan sampai Anda benar-benar menetapkan salah satu indeksnya.
chepner
@ John1024: Terima kasih atas jawaban ini. Bisakah Anda mengembangkannya untuk memasukkan jawaban yang disarankan untuk mencapai hasil yang diinginkan?
Anthony Webber
@AnthonyWebber Sure. Saya menambahkan bagian ke jawaban untuk menunjukkan cara memaksa nomor baru indeks.
John1024
2
Hanya menyebutkan pendekatan alternatif (yang mungkin lebih cocok untuk beberapa kode): alih-alih unset numbers[4], tetapkan seluruh array menggunakan slicing, yaitu numbers=("${numbers[@]:0:4}" "${numbers[@]:5}")(saya akan memposting sebagai jawaban, tetapi tidak punya waktu untuk menjelaskan dengan benar).
Toby Speight
@ John1024: Menghargai Anda melakukan itu. Dan terima kasih Toby :)
Anthony Webber
5

basharray seperti di ksh, tidak benar-benar array, mereka lebih seperti array asosiatif dengan kunci terbatas pada bilangan bulat positif (atau disebut array jarang ). Untuk shell dengan array nyata, Anda dapat melihat di kerang seperti rc, es, fish, yash, zsh(atau bahkan csh/ tcshmeskipun mereka kerang memiliki begitu banyak masalah mereka lebih baik dihindari).

Dalam 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

(Perhatikan bahwa dalam zsh, unset 'a[3]'sebenarnya setel ke string kosong untuk meningkatkan kompatibilitas dengan ksh)

di 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

di fish(bukan shell Bourne-like yang bertentangan dengan 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

dalam es(berdasarkan rc, tidak seperti 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})

di kshdanbash

Anda dapat menggunakan array sebagai array normal jika Anda melakukannya:

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

setelah setiap operasi hapus atau masukkan yang mungkin membuat daftar indeks tidak bersebelahan atau tidak mulai dari 0. Juga perhatikan bahwa ksh/ basharray mulai dari 0, bukan 1 (kecuali untuk $@(dalam beberapa hal)).

Itu akan berlaku merapikan elemen dan memindahkannya ke indeks 0, 1, 2 ... secara berurutan.

Perhatikan juga bahwa Anda perlu mengutip number[i]dalam:

unset 'number[i]'

Kalau tidak, itu akan diperlakukan seperti unset numberiada file yang dipanggil numberidi direktori saat ini.

Stéphane Chazelas
sumber