Periksa apakah array Bash berisi nilai

443

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.

Paolo Tedesco
sumber

Jawaban:

457

Pendekatan ini memiliki keuntungan karena tidak perlu mengulang semua elemen (setidaknya tidak secara eksplisit). Tetapi karena array_to_string_internal()dalam array.c masih mengulangi elemen array dan menggabungkannya menjadi string, itu mungkin tidak lebih efisien daripada solusi looping yang diusulkan, tetapi lebih mudah dibaca.

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array doesn't contain value
fi

Perhatikan bahwa dalam kasus di mana nilai yang Anda cari adalah salah satu kata dalam elemen array dengan spasi, itu akan memberikan positif palsu. Sebagai contoh

array=("Jack Brown")
value="Jack"

Regex akan melihat "Jack" berada di dalam array walaupun sebenarnya tidak. Jadi Anda harus mengubah IFSdan karakter pemisah pada regex Anda jika Anda masih ingin menggunakan solusi ini, seperti ini

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

Ini akan mencetak "false".

Jelas ini juga dapat digunakan sebagai pernyataan uji, yang memungkinkannya untuk dinyatakan sebagai satu-liner

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
sumber
1
Saya menambahkan spasi pada awal pertandingan nilai regex pertama, sehingga hanya cocok dengan kata, bukan sesuatu yang berakhir dengan kata. Bagus sekali. Namun, saya tidak mengerti mengapa Anda menggunakan kondisi kedua, bukankah yang pertama bekerja dengan baik saja?
JStrahl
1
@AwQiruiGuo Saya tidak yakin saya mengikuti. Apakah Anda berbicara tentang array dengan liter dolar? Jika demikian, pastikan untuk melepaskan diri dari nilai dolar yang cocok dengan Anda dengan garis miring terbalik.
Keegan
10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda
3
Shellcheck mengeluh tentang solusi ini, SC2199 dan SC2076. Saya tidak bisa memperbaiki peringatan tanpa merusak fungsi. Adakah pemikiran tentang hal itu selain menonaktifkan shellcheck untuk baris itu?
Ali Essam
4
SC2076 mudah diperbaiki, cukup hapus tanda kutip ganda di 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.
Keegan
388

Di bawah ini adalah fungsi kecil untuk mencapai ini. String pencarian adalah argumen pertama dan sisanya adalah elemen array:

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

Uji coba fungsi itu dapat terlihat seperti:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
patrik
sumber
5
Bekerja dengan baik! Aku hanya harus ingat untuk lulus array sebagai dengan kutipan: "${array[@]}". Kalau tidak, elemen yang mengandung spasi akan merusak fungsionalitas.
Juve
26
Bagus. Saya akan menyebutnya elemenIn () karena memeriksa apakah argumen pertama terkandung dalam yang kedua. berisiElements () terdengar seperti array akan pergi dulu. Untuk pemula seperti saya, contoh cara menggunakan fungsi yang tidak menulis ke stdout dalam pernyataan "jika" akan membantu: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Terima kasih atas bantuan Anda!
GlenPeterson
5
@Bluz konstruksi && adalah operator boolean DAN. Penggunaan operator boolean menciptakan pernyataan boolean. Logika Boolean mengatakan seluruh pernyataan hanya bisa benar jika kedua pernyataan sebelum dan sesudah && dievaluasi menjadi benar. Ini digunakan sebagai pintasan insted dan jika blok. Tes dievaluasi dan jika salah, tidak perlu mengevaluasi pengembalian karena tidak relevan dengan seluruh pernyataan setelah tes gagal dan karena itu tidak berjalan. Jika tes berhasil, maka keberhasilan pernyataan boolean TIDAK memerlukan hasil dari pengembalian yang ditentukan sehingga kode dijalankan.
petech
4
@James oleh konvensi kode sukses di bash adalah "0" dan kesalahan adalah segalanya> = 1. Inilah sebabnya mengapa mengembalikan 0 pada kesuksesan. :)
tftd
11
@Stelios shiftmenggeser daftar argumen dengan 1 ke kiri (menjatuhkan argumen pertama) dan fortanpa secara inimplisit mengulangi daftar argumen.
Christian
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
ghostdog74
sumber
69
Perhatikan bahwa ini tidak mengulangi setiap elemen dalam array secara terpisah ... melainkan hanya menggabungkan array dan mencocokkan "dua" sebagai substring. Ini dapat menyebabkan perilaku yang tidak diinginkan jika seseorang menguji apakah kata yang tepat "dua" adalah elemen dalam array.
MartyMacGyver
Saya pikir ini akan bekerja untuk saya dalam membandingkan jenis file tetapi menemukan bahwa ketika penghitung meningkat itu menghitung terlalu banyak nilai ... boo!
Mike Q
17
salah! Alasan: case "${myarray[@]}" in *"t"*) echo "found" ;; esackeluaran:found
Sergej Jevsejev
@ MartyMacGyver, bisakah Anda melihat tambahan saya pada jawaban ini stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin
45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Untuk string:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
Scott
sumber
Yang mengatakan, Anda dapat menggunakan diindeks untuk loop dan menghindari terbunuh ketika elemen array mengandung IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb
@Matt: Anda harus berhati-hati menggunakan ${#}karena Bash mendukung array jarang.
Dijeda sampai pemberitahuan lebih lanjut.
@ Paolo, jika array Anda berisi spasi maka bandingkan saja sebagai string. sebuah spasi adalah string juga.
Scott
@ Paolo: Anda bisa menjadikannya fungsi, tetapi array tidak dapat dilewatkan sebagai argumen sehingga Anda harus memperlakukannya sebagai global.
Dijeda sampai pemberitahuan lebih lanjut.
Dennis benar. Dari manual referensi bash: "Jika kata tersebut dikutip dua kali lipat, ... $ {name [@]} memperluas setiap elemen nama menjadi kata yang terpisah"
mkb
37

Solusi satu baris

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

Penjelasan

The printfpernyataan mencetak setiap elemen array pada baris terpisah.

The greppernyataan menggunakan karakter khusus ^dan $untuk menemukan baris yang berisi persis pola diberikan sebagai mypattern(tidak lebih, tidak kurang).


Pemakaian

Untuk memasukkan ini ke dalam if ... thenpernyataan:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

Saya menambahkan -qbendera ke grepekspresi sehingga tidak akan mencetak yang cocok; itu hanya akan memperlakukan keberadaan kecocokan sebagai "benar."

JellicleCat
sumber
Solusi bagus! Pada GNU grep, ada juga "--line-regexp" yang bisa menggantikan "-P" dan ^ dan $ dalam pola: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

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:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

Maka Anda dapat menggunakannya seperti ini:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

Dan uji keanggotaan seperti:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

Atau juga:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

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:

echo "<< ${myarray_index[$member]} >> is the index of $member"
LeoRochael
sumber
+1 untuk gagasan bahwa Anda harus menggunakan array asosiatif. Saya pikir kode untuk make_indexsedikit lebih dibikin karena tipuan; Anda bisa menggunakan nama array tetap dengan kode yang lebih sederhana.
musiphil
17

Saya biasanya hanya menggunakan:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

nilai bukan nol menunjukkan kecocokan ditemukan.

Sean DiSanti
sumber
Benar, ini jelas merupakan solusi termudah - harus ditandai jawaban menurut pendapat saya. Setidaknya miliki upvote saya! [:
ToVine
2
Itu tidak akan bekerja untuk jarum yang serupa. Misalnya,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Sangat benar. bergabung dengan pembatas yang tidak ada dalam elemen apa pun dan menambahkannya ke jarum akan membantu dengan itu. Mungkin sesuatu seperti ... (belum diuji)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
Menggunakan grep -x akan menghindari kesalahan positif: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher
Mungkin hanya inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)karena -x menyebabkan grep mencoba dan mencocokkan keseluruhan string input
MI Wright
17

Satu lagi liner tanpa fungsi:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

Terima kasih @Qwerty atas informasi tentang spasi!

fungsi yang sesuai:

find_in_array() {
  local word=$1
  shift
  for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
  return 1
}

contoh:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
sumber
1
Mengapa kita perlu subkulit di sini?
codeforester
1
@ kodeforester ini sudah tua ... tetapi seperti yang tertulis, Anda memerlukannya untuk melepaskannya, itulah yang exit 0dilakukannya (berhenti dengan segera jika ditemukan).
estani
Akhir dari satu liner seharusnya || echo not foundbukan || not foundatau shell akan mencoba mengeksekusi perintah dengan nama tidak dengan argumen ditemukan jika nilai yang diminta tidak ada dalam array.
zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Sekarang menangani array kosong dengan benar.

Yann
sumber
Bagaimana ini berbeda dari jawaban @ patrik? Satu-satunya perbedaan yang saya lihat adalah "$e" = "$1"(bukan "$e" == "$1") yang terlihat seperti bug.
CivFan
1
Bukan itu. @ patrik menggabungkan komentar saya dalam jawaban aslinya saat itu (tambalan # 4). Catatan: "e" == "$1"secara sintaksis lebih jelas.
Yann
@CivFan Dalam bentuk saat ini, ini lebih pendek daripada yang ada di jawaban patrik, karena $ {@: 2} yang elegan dan self mendokumentasikan $ 1. Saya ingin menambahkan bahwa mengutip tidak diperlukan dalam [[]].
Hontvári Levente
9

Ini adalah kontribusi kecil:

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

Catatan: cara ini tidak membedakan huruf "dua kata" tetapi ini tidak diperlukan dalam pertanyaan.

hornetbzz
sumber
Yang ini banyak membantu saya. Terima kasih!
Ed Manet
Pertanyaannya tidak secara eksplisit mengatakan Anda harus memberikan jawaban yang benar tetapi saya pikir itu tersirat dalam pertanyaan ... Array tidak mengandung nilai "dua".
tetsujin
Di atas akan melaporkan kecocokan untuk 'rd'.
Noel Yap
6

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.

array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

Ini akan menampilkan "Memeriksa" dan "Mencocokkan". Dengan array=(word "two words" something)itu hanya akan menampilkan "Memeriksa". Dengan array=(word "two widgets" something)tidak akan ada output.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
Mengapa tidak ganti saja wordsdengan regex ^words$yang hanya cocok dengan seluruh string, yang sepenuhnya menghilangkan kebutuhan untuk memeriksa setiap item secara individual?
Dejay Clayton
@ DjayClayton: Karena 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.
Dijeda sampai pemberitahuan lebih lanjut.
Ah, saya mengerti apa yang Anda coba lakukan. Saya telah mengusulkan jawaban varian yang lebih berkinerja dan aman.
Dejay Clayton
6

Ini bekerja untuk saya:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

Contoh panggilan:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi
Chris Prince
sumber
5
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

Jika mau, Anda dapat menggunakan opsi panjang yang setara:

--fixed-strings --quiet --line-regexp --null-data
Steven Penny
sumber
1
Ini tidak berfungsi dengan BSD-grep di Mac, karena tidak ada --null-data. :(
Will
4

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.

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

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:

  1. Bangun string perbandingan dengan menggunakan Bash's built-in printfshell-quoteing %q,. Mengutip shell akan memastikan bahwa karakter khusus menjadi "shell-safe" dengan lolos dengan backslash \.
  2. Pilih karakter khusus untuk dijadikan pembatas nilai. Pembatas harus menjadi salah satu karakter khusus yang akan lolos ketika menggunakan %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.
  3. Gabungkan semua elemen array menjadi satu string, menggunakan dua contoh karakter khusus untuk berfungsi sebagai pembatas. Menggunakan koma sebagai contoh, saya gunakan ,,%qsebagai argumen untuk printf. 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.
  4. Tambahkan dua instance trailing pembatas ke string, untuk memungkinkan kecocokan dengan elemen terakhir dari array. Jadi, bukannya membandingkan${array_str} , membandingkan dengan ${array_str},,.
  5. Jika string target yang Anda cari disediakan oleh variabel pengguna, Anda harus menghindari semua contoh karakter khusus dengan garis miring terbalik. Jika tidak, kecocokan ekspresi reguler menjadi rentan untuk dibodohi oleh elemen array yang dibuat dengan cerdik.
  6. Lakukan kecocokan ekspresi reguler Bash melawan string.
Dejay Clayton
sumber
Sangat pintar. Saya dapat melihat bahwa sebagian besar masalah potensial dapat dicegah, tetapi saya ingin menguji untuk melihat apakah ada kasus sudut. Juga, saya ingin melihat contoh penanganan poin 5. Sesuatu seperti printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]mungkin.
Dijeda sampai pemberitahuan lebih lanjut.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino
3

Tambahan kecil untuk jawaban @ ghostdog74 tentang menggunakan caselogika untuk memeriksa array yang mengandung nilai tertentu:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

Atau dengan extglobopsi dihidupkan, Anda dapat melakukannya seperti ini:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

Kita juga bisa melakukannya dengan ifpernyataan:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
Aleksandr Podkutin
sumber
2

diberikan:

array=("something to search for" "a string" "test2000")
elem="a string"

kemudian cek sederhana:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

dimana

c is element separator
p is regex pattern

(Alasan untuk menetapkan p secara terpisah, daripada menggunakan ekspresi langsung di dalam [[]] adalah untuk mempertahankan kompatibilitas untuk bash 4)

Beorn Harris
sumber
cintai penggunaan kata "sederhana" di sini ... 😂
Christian
2

Menggabungkan beberapa ide yang disajikan di sini Anda dapat membuat statment elegan jika tanpa loop yang cocok dengan kata yang tepat .

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

Ini tidak akan memicu pada wordatau val, hanya seluruh kata yang cocok. Ini akan rusak jika setiap nilai array mengandung banyak kata.

Ecker00
sumber
1

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:

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

Dengan ini, contoh pertanyaan menjadi:

array_contains A "one" && echo "contains one"

dll.

Barry Kelly
sumber
Dapatkah seseorang memposting contoh ini digunakan dalam if, khususnya bagaimana Anda meneruskan dalam array. Saya mencoba memeriksa untuk melihat apakah argumen terhadap skrip disahkan dengan memperlakukan params sebagai array, tetapi tidak mau bekerja. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' jika [[$ {check} == 1]]; lalu .... Tetapi ketika menjalankan skrip dengan 'asas' sebagai argumen, ia terus mengatakan asas: perintah tidak ditemukan. : /
Steve Childs
1

Menggunakan grepdanprintf

Format masing-masing anggota array pada baris baru, lalu grep baris.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
contoh:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

Perhatikan bahwa ini tidak memiliki masalah dengan delimeter dan spasi.

Qwerty
sumber
1

Pemeriksaan satu baris tanpa 'grep' dan loop

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

Pendekatan ini tidak menggunakan utilitas eksternal seperti grepmaupun loop.

Apa yang terjadi di sini, adalah:

  • kami menggunakan pencocokan substring wildcard untuk menemukan item kami dalam array yang digabungkan menjadi string;
  • kami memotong kemungkinan kesalahan positif dengan melampirkan item pencarian kami di antara sepasang pembatas;
  • kami menggunakan karakter yang tidak dapat dicetak sebagai pembatas, agar amannya;
  • kami mencapai pembatas kami yang digunakan untuk rangkaian array juga dengan penggantian sementara dari IFS variabel;
  • kami membuat IFSpenggantian nilai ini sementara dengan mengevaluasi ekspresi kondisional kami dalam sub-shell (di dalam sepasang kurung)
Sergey Ushakov
sumber
Hilangkan dlm. Gunakan IFS secara langsung.
Robin A. Meade
Ini jawaban terbaik. Saya sangat menyukainya, saya menulis fungsi menggunakan teknik ini .
Robin A. Meade
1

Menggunakan ekspansi parameter:

$ {parameter: + word} Jika parameter nol atau tidak disetel, tidak ada yang diganti, sebaliknya ekspansi kata diganti.

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done
Gabriel Laden
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.
Eric
0

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.

array=("word" "two words") # let's look for "two words"

menggunakan grepdan printf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

menggunakan for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

Untuk hasil not_found tambahkan || <run_your_if_notfound_command_here>

Qwerty
sumber
0

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.

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

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 $IFSuntuk 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 %quntuk memastikan bahwa data input lolos sehingga mereka dapat dengan aman digunakan sebagai kunci larik.

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

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. :-)

ghoti
sumber
Bagaimana jika array berisi tanda kurung?
gniourf_gniourf
@ gniourf_gniourf - tampaknya baik-baik saja jika tanda kurung seimbang, tapi saya bisa melihatnya menjadi masalah jika array Anda menyertakan nilai dengan tanda kurung kotak tidak seimbang. Dalam hal ini saya akan memohon evalinstruksi di akhir jawaban. :)
ghoti
Bukannya saya tidak suka eval(saya tidak menentangnya, tidak seperti kebanyakan orang yang menangis evaladalah kejahatan, kebanyakan tanpa memahami apa yang jahat tentang itu). Hanya saja perintahmu rusak. Mungkin %qalih-alih %sakan lebih baik.
gniourf_gniourf
1
@ gniourf_gniourf: Saya hanya bermaksud "pendekatan lain" sedikit (dan saya benar-benar dengan Anda kembali eval, jelas), tetapi Anda benar-benar benar, %qtampaknya 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. Dengan a=(one "two " three), mirip dengan masalah Keegan: tidak hanya array_contains a "two "mendapatkan negatif palsu, tetapi array_contains a twomendapat positif palsu. Cukup mudah untuk diperbaiki dengan mengatur IFS.
ghoti
Mengenai ruang putih, bukan karena ada kutipan yang hilang? itu juga rusak dengan karakter glob. Saya pikir Anda menginginkan ini sebagai gantinya:, eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )dan Anda dapat membuang local 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, katakan x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )dan return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf
-1

Versi saya dari teknik ekspresi reguler yang sudah disarankan:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

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.

jmpp
sumber
-1

Inilah pendapat saya tentang masalah ini. Ini versi singkatnya:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

Dan versi panjangnya, yang menurut saya jauh lebih mudah di mata.

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

Contoh:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
robert
sumber
Saya belum pernah menggunakan bash untuk waktu yang lama sekarang karena saya kesulitan memahami jawaban, atau bahkan apa yang saya tulis sendiri :) Saya tidak percaya bahwa pertanyaan ini masih mendapatkan aktivitas setelah sekian lama :)
Paolo Tedesco
Bagaimana dengan test_arr=("hello" "world" "two words")?
Qwerty
-1

Saya memiliki kasus yang harus saya periksa apakah ID terkandung dalam daftar ID yang dihasilkan oleh skrip / perintah lain. Bagi saya bekerja sebagai berikut:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

Anda juga dapat mempersingkat / memadatkannya seperti ini:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

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 ...

E. Körner
sumber
-2

Kode berikut memeriksa apakah nilai yang diberikan dalam array dan mengembalikan offset berbasis nol:

A=("one" "two" "three four")
VALUE="two"

if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
  echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
  echo "Couldn't find $VALUE"
fi

Pertandingan dilakukan pada nilai lengkap, oleh karena itu pengaturan VALUE = "tiga" tidak akan cocok.

Sven Rieke
sumber
-2

Ini mungkin layak diselidiki jika Anda tidak ingin mengulangi:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

Cuplikan diadaptasi dari: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Saya pikir ini cukup pintar.

EDIT: Anda mungkin bisa melakukan:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

Tetapi yang terakhir hanya berfungsi jika array berisi nilai unik. Mencari 1 dalam "143" akan memberikan false positive, methinks.

Sigg3.net
sumber
-2

Sedikit terlambat, tetapi Anda bisa menggunakan ini:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi
Coder-256
sumber
-2

Saya datang dengan yang satu ini, yang ternyata hanya bekerja di zsh, tapi saya pikir pendekatan umumnya bagus.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

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:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

Setelah substitusi itu membandingkan panjang kedua array. Obly jika array berisi elemen substitusi akan sepenuhnya menghapusnya, dan jumlah akan berbeda.

spelufo
sumber