Di Bash, apa cara paling sederhana untuk menguji apakah array berisi nilai tertentu?
Sunting : Dengan bantuan dari jawaban dan komentar, setelah beberapa pengujian, saya datang dengan ini:
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
echo "contains three"
fi
Saya tidak yakin apakah itu solusi terbaik, tetapi tampaknya berhasil.
[[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
if
. Saya tidak berpikir ada cara untuk menghindari SC2199 dengan pendekatan ini. Anda harus secara eksplisit mengulang array, seperti yang ditunjukkan dalam beberapa solusi lain, atau mengabaikan peringatan.Di bawah ini adalah fungsi kecil untuk mencapai ini. String pencarian adalah argumen pertama dan sisanya adalah elemen array:
Uji coba fungsi itu dapat terlihat seperti:
sumber
"${array[@]}"
. Kalau tidak, elemen yang mengandung spasi akan merusak fungsionalitas.if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi;
Terima kasih atas bantuan Anda!shift
menggeser daftar argumen dengan 1 ke kiri (menjatuhkan argumen pertama) danfor
tanpa secarain
implisit mengulangi daftar argumen.sumber
case "${myarray[@]}" in *"t"*) echo "found" ;; esac
keluaran:found
Untuk string:
sumber
${#}
karena Bash mendukung array jarang.Solusi satu baris
Penjelasan
The
printf
pernyataan mencetak setiap elemen array pada baris terpisah.The
grep
pernyataan menggunakan karakter khusus^
dan$
untuk menemukan baris yang berisi persis pola diberikan sebagaimypattern
(tidak lebih, tidak kurang).Pemakaian
Untuk memasukkan ini ke dalam
if ... then
pernyataan:Saya menambahkan
-q
bendera kegrep
ekspresi sehingga tidak akan mencetak yang cocok; itu hanya akan memperlakukan keberadaan kecocokan sebagai "benar."sumber
Jika Anda membutuhkan kinerja, Anda tidak ingin mengulang seluruh array Anda setiap kali Anda mencari.
Dalam hal ini, Anda bisa membuat array asosiatif (tabel hash, atau kamus) yang mewakili indeks array itu. Yaitu memetakan setiap elemen array ke dalam indeks dalam array:
Maka Anda dapat menggunakannya seperti ini:
Dan uji keanggotaan seperti:
Atau juga:
Perhatikan bahwa solusi ini melakukan hal yang benar bahkan jika ada spasi dalam nilai yang diuji atau dalam nilai array.
Sebagai bonus, Anda juga mendapatkan indeks nilai dalam array dengan:
sumber
make_index
sedikit lebih dibikin karena tipuan; Anda bisa menggunakan nama array tetap dengan kode yang lebih sederhana.Saya biasanya hanya menggunakan:
nilai bukan nol menunjukkan kecocokan ditemukan.
sumber
haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)
karena -x menyebabkan grep mencoba dan mencocokkan keseluruhan string inputSatu lagi liner tanpa fungsi:
Terima kasih @Qwerty atas informasi tentang spasi!
fungsi yang sesuai:
contoh:
sumber
exit 0
dilakukannya (berhenti dengan segera jika ditemukan).|| echo not found
bukan|| not found
atau shell akan mencoba mengeksekusi perintah dengan nama tidak dengan argumen ditemukan jika nilai yang diminta tidak ada dalam array.Sekarang menangani array kosong dengan benar.
sumber
"$e" = "$1"
(bukan"$e" == "$1"
) yang terlihat seperti bug."e" == "$1"
secara sintaksis lebih jelas.Ini adalah kontribusi kecil:
Catatan: cara ini tidak membedakan huruf "dua kata" tetapi ini tidak diperlukan dalam pertanyaan.
sumber
Jika Anda ingin melakukan tes cepat dan kotor untuk melihat apakah layak untuk mengulangi keseluruhan array, Bash dapat memperlakukan array seperti skalar. Tes untuk kecocokan dalam skalar, jika tidak ada maka melewatkan loop menghemat waktu. Jelas Anda bisa mendapatkan hasil positif palsu.
Ini akan menampilkan "Memeriksa" dan "Mencocokkan". Dengan
array=(word "two words" something)
itu hanya akan menampilkan "Memeriksa". Denganarray=(word "two widgets" something)
tidak akan ada output.sumber
words
dengan regex^words$
yang hanya cocok dengan seluruh string, yang sepenuhnya menghilangkan kebutuhan untuk memeriksa setiap item secara individual?pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]
tidak akan pernah cocok karena memeriksa seluruh array sekaligus seolah-olah itu skalar. Pemeriksaan individu dalam jawaban saya harus dilakukan hanya jika ada alasan untuk melanjutkan berdasarkan pertandingan kasar.Ini bekerja untuk saya:
Contoh panggilan:
sumber
Jika mau, Anda dapat menggunakan opsi panjang yang setara:
sumber
Meminjam dari Dennis Williamson 's jawabannya , berikut solusi menggabungkan array, shell-aman mengutip, dan ekspresi reguler untuk menghindari kebutuhan untuk: iterasi loop; menggunakan pipa atau sub-proses lainnya; atau menggunakan utilitas non-bash.
Kode di atas berfungsi dengan menggunakan ekspresi reguler Bash untuk mencocokkan dengan versi konten array yang diketikkan. Ada enam langkah penting untuk memastikan bahwa kecocokan ekspresi reguler tidak dapat dibodohi oleh kombinasi nilai yang cerdas dalam array:
printf
shell-quoteing%q
,. Mengutip shell akan memastikan bahwa karakter khusus menjadi "shell-safe" dengan lolos dengan backslash\
.%q
; itulah satu-satunya cara untuk menjamin bahwa nilai-nilai dalam array tidak dapat dibangun dengan cara yang cerdas untuk mengelabui kecocokan ekspresi reguler. Saya memilih koma,
karena karakter itu adalah yang paling aman ketika dievaluasi atau disalahgunakan dengan cara yang tidak terduga.,,%q
sebagai argumen untukprintf
. Ini penting karena dua instance dari karakter khusus hanya dapat muncul di samping satu sama lain ketika mereka muncul sebagai pembatas; semua contoh lain dari karakter khusus akan lolos.${array_str}
, membandingkan dengan${array_str},,
.sumber
printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]
mungkin.case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tambahan kecil untuk jawaban @ ghostdog74 tentang menggunakan
case
logika untuk memeriksa array yang mengandung nilai tertentu:Atau dengan
extglob
opsi dihidupkan, Anda dapat melakukannya seperti ini:Kita juga bisa melakukannya dengan
if
pernyataan:sumber
diberikan:
kemudian cek sederhana:
dimana
(Alasan untuk menetapkan p secara terpisah, daripada menggunakan ekspresi langsung di dalam [[]] adalah untuk mempertahankan kompatibilitas untuk bash 4)
sumber
Menggabungkan beberapa ide yang disajikan di sini Anda dapat membuat statment elegan jika tanpa loop yang cocok dengan kata yang tepat .
Ini tidak akan memicu pada
word
atauval
, hanya seluruh kata yang cocok. Ini akan rusak jika setiap nilai array mengandung banyak kata.sumber
Saya biasanya menulis utilitas semacam ini untuk beroperasi atas nama variabel, bukan nilai variabel, terutama karena bash tidak dapat meneruskan variabel dengan referensi.
Ini versi yang berfungsi dengan nama array:
Dengan ini, contoh pertanyaan menjadi:
dll.
sumber
Menggunakan
grep
danprintf
Format masing-masing anggota array pada baris baru, lalu
contoh:grep
baris.Perhatikan bahwa ini tidak memiliki masalah dengan delimeter dan spasi.
sumber
Pemeriksaan satu baris tanpa 'grep' dan loop
Pendekatan ini tidak menggunakan utilitas eksternal seperti
grep
maupun loop.Apa yang terjadi di sini, adalah:
IFS
variabel;IFS
penggantian nilai ini sementara dengan mengevaluasi ekspresi kondisional kami dalam sub-shell (di dalam sepasang kurung)sumber
Menggunakan ekspansi parameter:
sumber
${myarray[hello]:+_}
bekerja sangat baik untuk array associaive, tetapi tidak untuk array yang diindeks biasa. Pertanyaannya adalah tentang menemukan nilai dalam aray, tidak memeriksa apakah kunci array asosiatif ada.Setelah menjawab, saya membaca jawaban lain yang sangat saya sukai, tetapi ternyata cacat dan tidak disukai. Saya mendapat inspirasi dan di sini ada dua pendekatan baru yang saya anggap layak.
menggunakan
grep
danprintf
:menggunakan
for
:Untuk hasil not_found tambahkan
|| <run_your_if_notfound_command_here>
sumber
Inilah pendapat saya tentang ini.
Saya lebih suka tidak menggunakan bash untuk loop jika saya bisa menghindarinya, karena itu membutuhkan waktu untuk berjalan. Jika ada sesuatu yang harus diulang, biarkan itu menjadi sesuatu yang ditulis dalam bahasa tingkat yang lebih rendah daripada skrip shell.
Ini bekerja dengan membuat array asosiatif sementara
_arr
,, yang indeksnya diturunkan dari nilai-nilai array input. (Perhatikan bahwa array asosiatif tersedia di bash 4 dan di atas, jadi fungsi ini tidak akan berfungsi di versi bash yang lebih lama.) Kami menetapkan$IFS
untuk menghindari pemisahan kata di spasi putih.Fungsi tidak mengandung loop eksplisit, meskipun secara internal bash langkah melalui array input untuk mengisi
printf
. Format printf digunakan%q
untuk memastikan bahwa data input lolos sehingga mereka dapat dengan aman digunakan sebagai kunci larik.Perhatikan bahwa semua fungsi yang digunakan fungsi ini adalah bawaan untuk bash, jadi tidak ada pipa eksternal yang menyeret Anda ke bawah, bahkan dalam ekspansi perintah.
Dan jika Anda tidak suka menggunakan
eval
... yah, Anda bebas menggunakan pendekatan lain. :-)sumber
eval
instruksi di akhir jawaban. :)eval
(saya tidak menentangnya, tidak seperti kebanyakan orang yang menangiseval
adalah kejahatan, kebanyakan tanpa memahami apa yang jahat tentang itu). Hanya saja perintahmu rusak. Mungkin%q
alih-alih%s
akan lebih baik.eval
, jelas), tetapi Anda benar-benar benar,%q
tampaknya membantu, tanpa melanggar apa pun yang bisa saya lihat. (Saya tidak menyadari bahwa% q akan keluar dari kurung siku juga.) Masalah lain yang saya lihat dan perbaiki adalah mengenai spasi putih. Dengana=(one "two " three)
, mirip dengan masalah Keegan: tidak hanyaarray_contains a "two "
mendapatkan negatif palsu, tetapiarray_contains a two
mendapat positif palsu. Cukup mudah untuk diperbaiki dengan mengaturIFS
.eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )
dan Anda dapat membuanglocal IFS=
. Masih ada masalah dengan bidang kosong dalam array, karena Bash akan menolak untuk membuat kunci kosong di array asosiatif. Cara cepat dan cepat untuk memperbaikinya adalah dengan menambahkan karakter tiruan, katakanx
:eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )
danreturn $(( 1 - 0${_arr[x$2]} ))
.Versi saya dari teknik ekspresi reguler yang sudah disarankan:
Apa yang terjadi di sini adalah bahwa Anda memperluas seluruh array nilai yang didukung ke dalam kata-kata dan menambahkan string tertentu, "X-" dalam kasus ini, ke masing-masing, dan melakukan hal yang sama dengan nilai yang diminta. Jika yang ini benar-benar terdapat dalam array, maka string yang dihasilkan paling banyak akan cocok dengan salah satu token yang dihasilkan, atau tidak ada sama sekali sebaliknya. Dalam kasus terakhir | | Operator memicu dan Anda tahu Anda sedang berhadapan dengan nilai yang tidak didukung. Sebelum semua itu, nilai yang diminta dihapus dari semua spasi putih depan dan belakang melalui manipulasi string shell standar.
Ini bersih dan elegan, saya percaya, meskipun saya tidak terlalu yakin bagaimana performanya jika array nilai yang didukung Anda sangat besar.
sumber
Inilah pendapat saya tentang masalah ini. Ini versi singkatnya:
Dan versi panjangnya, yang menurut saya jauh lebih mudah di mata.
Contoh:
sumber
test_arr=("hello" "world" "two words")
?Saya memiliki kasus yang harus saya periksa apakah ID terkandung dalam daftar ID yang dihasilkan oleh skrip / perintah lain. Bagi saya bekerja sebagai berikut:
Anda juga dapat mempersingkat / memadatkannya seperti ini:
Dalam kasus saya, saya menjalankan jq untuk memfilter beberapa JSON untuk daftar ID dan kemudian harus memeriksa apakah ID saya ada di daftar ini dan ini bekerja dengan baik untuk saya. Ini tidak akan berfungsi untuk array yang dibuat secara manual dari tipe
LIST=("1" "2" "4")
tetapi untuk dengan output skrip yang dipisahkan baris baru.PS .: tidak dapat mengomentari jawaban karena saya relatif baru ...
sumber
Kode berikut memeriksa apakah nilai yang diberikan dalam array dan mengembalikan offset berbasis nol:
Pertandingan dilakukan pada nilai lengkap, oleh karena itu pengaturan VALUE = "tiga" tidak akan cocok.
sumber
Ini mungkin layak diselidiki jika Anda tidak ingin mengulangi:
Cuplikan diadaptasi dari: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Saya pikir ini cukup pintar.
EDIT: Anda mungkin bisa melakukan:
Tetapi yang terakhir hanya berfungsi jika array berisi nilai unik. Mencari 1 dalam "143" akan memberikan false positive, methinks.
sumber
Sedikit terlambat, tetapi Anda bisa menggunakan ini:
sumber
Saya datang dengan yang satu ini, yang ternyata hanya bekerja di zsh, tapi saya pikir pendekatan umumnya bagus.
Anda mengambil pola Anda dari setiap elemen hanya jika dimulai
${arr[@]/#pattern/}
atau diakhiri${arr[@]/%pattern/}
dengan itu. Dua pergantian ini bekerja di bash, tetapi keduanya pada saat yang sama${arr[@]/#%pattern/}
hanya berfungsi di zsh.Jika array yang dimodifikasi sama dengan yang asli, maka array tersebut tidak mengandung elemen.
Edit:
Yang ini bekerja di bash:
Setelah substitusi itu membandingkan panjang kedua array. Obly jika array berisi elemen substitusi akan sepenuhnya menghapusnya, dan jumlah akan berbeda.
sumber