GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana saya bisa mendapatkan nilai unik dari array di Bash?

Saya menyadari ini sudah dijawab, tetapi muncul cukup tinggi di hasil penelusuran, dan mungkin membantu seseorang.

printf "%s\n" "${IDS[@]}" | sort -u

Contoh:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>

Jika elemen array Anda memiliki spasi putih atau karakter khusus shell lainnya (dan dapatkah Anda yakin tidak?) maka untuk menangkapnya terlebih dahulu (dan Anda harus selalu melakukan ini) ekspresikan array Anda dalam tanda kutip ganda! misalnya "${a[@]}" . Bash secara harfiah akan menafsirkan ini sebagai "setiap elemen array dalam argumen yang terpisah ". Di dalam bash ini selalu berhasil, selalu.

Kemudian, untuk mendapatkan array yang diurutkan (dan unik), kita harus mengubahnya menjadi format yang dipahami pengurutan dan dapat mengubahnya kembali menjadi elemen array bash. Ini yang terbaik yang pernah saya buat:

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

Sayangnya, ini gagal dalam kasus khusus dari array kosong, mengubah array kosong menjadi array 1 elemen kosong (karena printf memiliki 0 argumen tetapi masih mencetak seolah-olah memiliki satu argumen kosong - lihat penjelasan). Jadi, Anda harus memahaminya dalam if atau something.

Penjelasan:Format %q untuk printf "Shell escapes" argumen yang dicetak, sedemikian rupa sehingga bash dapat pulih dalam sesuatu seperti eval! Karena setiap elemen dicetak Shell lolos pada barisnya sendiri, satu-satunya pemisah antar elemen adalah baris baru , dan penetapan larik mengambil setiap baris sebagai elemen, mem-parsing nilai yang diloloskan menjadi teks literal.

mis.

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

Eval diperlukan untuk menghapus pelolosan dari setiap nilai yang kembali ke array.


Jika Anda menjalankan Bash versi 4 atau lebih tinggi (yang seharusnya menjadi kasus di Linux versi modern mana pun), Anda bisa mendapatkan nilai array unik di bash dengan membuat array asosiatif baru yang berisi setiap nilai dari array asli. Sesuatu seperti ini:

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

Ini berfungsi karena dalam larik apa pun (asosiatif atau tradisional, dalam bahasa apa pun), setiap kunci hanya dapat muncul sekali. Ketika for loop tiba di nilai kedua dari aa di a[2] , itu menimpa b[aa] yang semula ditetapkan untuk a[0] .

Melakukan hal-hal di bash asli bisa lebih cepat daripada menggunakan pipa dan alat eksternal seperti sort dan uniq , meskipun untuk kumpulan data yang lebih besar Anda mungkin akan melihat kinerja yang lebih baik jika Anda menggunakan bahasa yang lebih canggih seperti awk, python, dll.

Jika Anda merasa percaya diri, Anda dapat menghindari for loop dengan menggunakan printf kemampuan untuk mendaur ulang formatnya untuk banyak argumen, meskipun ini tampaknya memerlukan eval . (Berhentilah membaca sekarang jika Anda tidak keberatan.)

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

Alasan solusi ini membutuhkan eval adalah bahwa nilai array ditentukan sebelum pemisahan kata. Artinya, output dari substitusi perintah dianggap satu kata bukan sekumpulan key=value pair.

Meskipun ini menggunakan subkulit, ia hanya menggunakan bawaan bash untuk memproses nilai array. Pastikan untuk mengevaluasi penggunaan eval Anda dengan pandangan kritis. Jika Anda tidak 100% yakin bahwa chepner atau glenn jackman atau greycat tidak akan menemukan kesalahan pada kode Anda, gunakan loop for sebagai gantinya.


Agak retas, tapi ini harus dilakukan:

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Untuk menyimpan kembali hasil unik yang diurutkan ke dalam array, lakukan tugas Array:

sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

Jika shell Anda mendukung string berikut (bash seharusnya), Anda dapat menyisihkan echo proses dengan mengubahnya menjadi:

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '

Catatan per 28 Agustus 2021:

Menurut ShellCheck wiki 2207 a read -a pipa harus digunakan untuk menghindari pemisahan. Jadi, dalam bash perintahnya adalah:

IFS=" " read -r -a ids <<< "$(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"

atau

IFS=" " read -r -a ids <<< "$(tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ')"

Masukan:

ids=(aa ab aa ac aa ad)

Keluaran:

aa ab ac ad

Penjelasan:

  • "${ids[@]}" - Sintaks untuk bekerja dengan larik shell, baik digunakan sebagai bagian dari echo atau herestring. @ bagian berarti "semua elemen dalam larik"
  • tr ' ' '\n' - Ubah semua spasi menjadi baris baru. Karena array Anda dilihat oleh Shell sebagai elemen pada satu baris, dipisahkan oleh spasi; dan karena sort mengharapkan input berada di baris terpisah.
  • sort -u - urutkan dan pertahankan hanya elemen unik
  • tr '\n' ' ' - ubah baris baru yang kita tambahkan sebelumnya kembali ke spasi.
  • $(...) - Pergantian Perintah
  • Selain itu:tr ' ' '\n' <<< "${ids[@]}" adalah cara yang lebih efisien untuk melakukan:echo "${ids[@]}" | tr ' ' '\n'

Linux
  1. Dapatkan Semua File Tapi File Dalam Array – Bash?

  2. Bagaimana Cara Memeriksa Apakah Bash Dapat Mencetak Warna?

  3. Cara mendeklarasikan array 2D di bash

  1. Bagaimana saya bisa mendapatkan informasi wadah Docker Linux dari dalam wadah itu sendiri?

  2. Bagaimana cara mendapatkan nama host dari IP (Linux)?

  3. Bagaimana saya bisa * hanya * mendapatkan jumlah byte yang tersedia pada disk di bash?

  1. Bagaimana Cara Membuat Array Elemen Unik Dari String/array Di Bash?

  2. Bagaimana saya bisa memfilter hasil unik dari keluaran grep?

  3. Bagaimana saya bisa mendapatkan biner dari file .py