Uji apakah elemen dalam array dalam bash

17

Apakah ada cara yang bagus untuk memeriksa apakah array memiliki elemen dalam bash (lebih baik daripada mengulang-ulang)?

Atau, apakah ada cara lain untuk memeriksa apakah angka atau string sama dengan salah satu set konstanta yang telah ditentukan?

Tgr
sumber

Jawaban:

24

Di Bash 4, Anda dapat menggunakan array asosiatif:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

Untuk mengatur array pada awalnya Anda juga bisa melakukan tugas langsung:

array[foo]=1
array[bar]=1
# etc.

atau dengan cara ini:

array=([foo]=1 [bar]=1 [baz]=1)
Dijeda sampai pemberitahuan lebih lanjut.
sumber
Sebenarnya, tes [[]] tidak berfungsi jika nilainya kosong. Misalnya, "array ['test'] = ''". Dalam kasus ini, kunci 'tes' ada, dan Anda dapat melihatnya terdaftar dengan $ {! Array [@]}, tetapi "[[$ {array ['test']}]]; echo $?" gema 1, bukan 0.
haridsv
1
${array[$test1]}sederhana tetapi memiliki masalah: itu tidak akan berfungsi jika Anda menggunakan set -uskrip Anda (yang disarankan), karena Anda akan mendapatkan "variabel tidak terikat".
tokland
@tokland: Siapa yang merekomendasikannya? Tentu saja tidak.
Dijeda sampai pemberitahuan lebih lanjut.
@ DennisWilliamson: Ok, beberapa orang merekomendasikannya, tapi saya pikir akan lebih baik untuk memiliki solusi yang berfungsi terlepas dari nilai flag-flag ini.
tokland
10

Ini adalah pertanyaan lama, tapi saya pikir apa solusi yang paling sederhana belum muncul belum: test ${array[key]+_}. Contoh:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

Output:

a is set
b is set

Untuk melihat bagaimana ini bekerja, periksa ini .

Tokland
sumber
2
Manual info menyarankan Anda untuk menggunakan envuntuk menghindari ambiguitas dalam alias, prog dan fungsi lain yang mungkin telah mengadopsi nama "test". Seperti di atas env test ${xs[a]+_} && echo "a is set". Anda juga bisa mendapatkan fungsionalitas ini menggunakan kurung ganda, trik yang sama kemudian memeriksa null:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski
Anda juga dapat menggunakan yang lebih sederhana[[ ${xs[b]+set} ]]
Arne L.
5

Ada cara untuk menguji apakah elemen array asosiatif ada (tidak disetel), ini berbeda dengan kosong:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

Kemudian gunakan:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Diego F. Durán
sumber
hanya sebuah catatan: menyatakan -A tidak bekerja pada bash 3.2.39 (debian lenny), tetapi bekerja pada bash 4.1.5 (debian squeeze)
Paweł Polewicz
Array asosiatif diperkenalkan di Bash 4.
Diego F. Durán
1
perhatikan itu if ! some_check then return 1= some_check. Jadi: isNotSet() { [[ ... ]] }. Periksa solusi saya di bawah ini, Anda dapat melakukannya dengan cek sederhana.
tokland
3

Anda dapat melihat apakah ada entri dengan memiparkan konten array ke grep.

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

Anda juga bisa mendapatkan indeks entri dengan grep -n, yang mengembalikan nomor baris pertandingan (ingat untuk mengurangi 1 untuk mendapatkan indeks berbasis nol) Ini akan cukup cepat kecuali untuk array yang sangat besar.

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

penjelasan:

  • $( ... ) sama dengan menggunakan backticks untuk menangkap output dari suatu perintah ke dalam variabel
  • printf output mydata satu elemen per baris
  • (semua tanda kutip diperlukan, bersama dengan @alih - alih *. ini, hindari membagi "hello world" menjadi 2 baris)
  • grepmencari string yang tepat: ^dan $mencocokkan awal dan akhir baris
  • grep -n mengembalikan baris #, dalam bentuk 4: hello world
  • grep -m 1 menemukan kecocokan pertama saja
  • cut ekstrak hanya nomor baris
  • kurangi 1 dari nomor baris yang dikembalikan.

Anda tentu saja dapat melipat pengurangan menjadi perintah. Tapi kemudian uji -1 untuk hilang:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) tidak bilangan bulat aritmatika
kane
sumber
1

Saya tidak berpikir Anda bisa melakukannya dengan benar tanpa perulangan kecuali Anda memiliki data yang sangat terbatas dalam array.

Berikut adalah satu varian sederhana, ini akan dengan benar mengatakan bahwa "Super User"ada dalam array. Tetapi itu juga akan mengatakan bahwa "uper Use"ada dalam array.

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

Masalahnya adalah bahwa tidak ada cara mudah untuk menambahkan jangkar (yang dapat saya pikirkan) selain perulangan melalui array. Kecuali jika Anda dapat menambahkannya sebelum memasukkannya ke dalam array ...

Nifle
sumber
Ini adalah solusi yang bagus ketika konstanta alfanumerik, meskipun (dengan grep "\b$FINDME\b"). Mungkin bisa bekerja dengan konstanta non-alfanumerik yang tidak memiliki spasi, dengan "(^| )$FINDME(\$| )"(atau sesuatu seperti itu ... Saya tidak pernah bisa belajar apa rasa penggunaan regexp grep.)
Tgr
1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi
Cong Nguyen
sumber
1
Bisakah Anda menguraikan mengapa Anda menyarankan pendekatan ini? OP bertanya apakah ada cara untuk melakukannya tanpa melalui array , yang Anda lakukan in_array. Cheers
bertieb
Yah, setidaknya loop itu di-capsuled dalam suatu fungsi, yang mungkin cukup baik untuk banyak kasus (dengan set data kecil), dan tidak memerlukan bash 4+. Mungkin ${ARRAY[@]}harus digunakan.
Tobias