Bash dizisinin bir değer içerip içermediğini kontrol edin


443

Bash'te, bir dizinin belirli bir değer içerip içermediğini test etmenin en basit yolu nedir?

Düzenleme : Cevaplar ve yorumlar yardımıyla, bazı testlerden sonra, ben bu ile geldi:

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

Bunun en iyi çözüm olup olmadığından emin değilim, ama işe yarıyor gibi görünüyor.

Yanıtlar:


458

Bu yaklaşımın avantajı, tüm elemanlar üzerinde döngü oluşturmaya gerek olmamasıdır (en azından açıkça değil). Fakat array_to_string_internal()içinde array.c hala bir dizeye dizi öğelerinin ve birleştirir onları döngüler, bu döngü çözümler önerilmiştir muhtemelen daha verimli değil, ama daha okunaklı bu.

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

Aradığınız değerin boşluklu bir dizi öğesindeki kelimelerden biri olduğu durumlarda yanlış pozitifler vereceğini unutmayın. Örneğin

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

Normal ifade, "Jack" ifadesini dizede bulunmadığı halde görecektir. Dolayısıyla IFS, bu çözümü kullanmaya devam etmek istiyorsanız, normal ifadenizdeki ayırıcı karakterleri değiştirmeniz gerekir.

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

Bu "false" yazdıracaktır.

Açıkçası bu, bir test ifadesi olarak da kullanılabilir ve bu da tek katmanlı olarak ifade edilmesini sağlar

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"

1
İlk normal ifade değeri eşleşmesinin başına bir boşluk ekledim, böylece sözcükle biten bir şeyle değil, yalnızca kelimeyle eşleşir. Harika çalışıyor. Ancak, ikinci koşulu neden kullandığınızı anlamıyorum, ilk işi tek başına iyi yapmaz mıydınız?
JStrahl

1
@AwQiruiGuo Takip ettiğimden emin değilim. Dolar değişmezine sahip dizilerden mi bahsediyorsunuz? Eğer öyleyse, ters eğik çizgilerle eşleştirdiğiniz değerdeki dolarlardan kaçmayı unutmayın.
Keegan

10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda

3
Shellcheck, SC2199 ve SC2076 çözümlerinden şikayet ediyor. İşlevi bozmadan uyarıları düzeltemedim. Bu satır için kabuk kontrolünü devre dışı bırakmaktan başka düşünceleriniz var mı?
Ali Essam

4
SC2076'nın düzeltilmesi kolaydır, sadece içindeki çift tırnak işaretlerini kaldırın if. Bu yaklaşımla SC2199'dan kaçınmanın bir yolu olduğunu sanmıyorum . Diğer çözümlerin bazılarında gösterildiği gibi dizi üzerinden açıkça döngü yapmanız veya uyarıyı yoksaymanız gerekir.
Keegan

388

Aşağıda bunu başarmak için küçük bir işlev bulunmaktadır. Arama dizesi ilk bağımsız değişkendir ve geri kalanı dizi öğeleridir:

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

Bu işlevin bir test çalıştırması şöyle görünebilir:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1

5
Güzel çalışıyor! Sadece tırnak olarak diziyi geçmek hatırlamak zorunda: "${array[@]}". Aksi takdirde boşluk içeren öğeler işlevselliği bozacaktır.
Juve

26
Güzel. İlk argümanın ikincide yer alıp almadığını kontrol ettiği için elementIn () diyorum. Dizi ilk önce olduğu gibi ihtiva eder. Benim gibi yeni başlayanlar için, bir "if" deyiminde stdout'a yazmayan bir fonksiyonun nasıl kullanılacağına bir örnek yardımcı olacaktır: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Yardımınız için teşekkürler!
GlenPeterson

5
@Bluz && construct, bir boole AND operatörüdür. Boole işleçlerinin kullanımı bir boolean deyimi oluşturur. Bu, test edilen bir blok kısayolu olarak kullanılır. Test değerlendirilir ve yanlışsa, test başarısız olduktan ve çalışmadığında bir kez tüm ifadeyle ilgisi olmadığı için dönüşü değerlendirmeye gerek yoktur. Test başarılı olursa, Boes ifadesinin başarısı DOES, kodun çalıştırılması için dönüşün sonucunun belirlenmesini gerektirir.
peteches

4
@ Kongre gereği bash'daki başarı kodu "0" ve hata her şey> = 1'dir. Bu yüzden başarıda 0 döndürür. :)
tftd

11
@Stelios shiftkaymalar (ilk argüman bırakarak) sola 1 ile argüman listesi ve forbir olmadan inörtük argüman listesinin üzerinde dolaşır.
Christian

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

69
Bunun dizideki her öğe üzerinde ayrı ayrı yinelenmediğini unutmayın ... bunun yerine diziyi birleştirir ve "iki" ile bir alt dize olarak eşleşir. Eğer biri "iki" kelimesinin dizideki bir eleman olup olmadığını test ederse, bu istenmeyen davranışlara neden olabilir.
MartyMacGyver

Bunun dosya türlerini karşılaştırmada benim için çalışacağını düşündüm ama sayaçlar arttıkça çok fazla değer saydığını buldum ... boo!
Mike Q

17
yanlış! Sebep: case "${myarray[@]}" in *"t"*) echo "found" ;; esacçıktılar:found
Sergej Jevsejev

@MartyMacGyver, lütfen bu cevaba
eklediğime bakabilir misiniz

45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Dizeler için:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done

Bununla birlikte, bir dizi öğesi IFS içerdiğinde döngü için dizinlenmiş bir dizini kullanabilir ve öldürülmekten kaçınabilirsiniz: for ((i = 0; i <$ {# dizi [@]}; i ++))
mkb

@Matt: ${#}Bash seyrek dizileri desteklediğinden, kullanırken dikkatli olmalısınız .
sonraki duyuruya kadar duraklatıldı.

@Paolo, diziniz bir boşluk içeriyorsa, dizeyi dize olarak karşılaştırın. boşluk da bir dizedir.
Scott

@Paolo: Bunu bir işlev haline getirebilirsiniz, ancak diziler argüman olarak iletilemez, bu yüzden ona global olarak davranmanız gerekir.
sonraki duyuruya kadar duraklatıldı.

Dennis haklı. Bash referans kılavuzundan: "Sözcük çift tırnak içine
alınmışsa

37

Tek hat çözümü

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

açıklama

printfBildirimi ayrı bir satırda dizinin her öğesi basar.

grepİfadesi özel karakterleri kullanır ^ve $içeren bir çizgi bulmak için tam olarak verilen desen mypattern(artık, az).


kullanım

Bunu bir if ... thenifadeye koymak için :

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

Eşleşmeleri yazdırmaması -qiçin grepifadeye bir bayrak ekledim ; sadece bir maçın varlığını "doğru" olarak ele alacaktır.


Güzel çözüm! GNU grep'te, "-P" ve örüntüde ^ ve $ yerine geçebilecek "--line-regexp" de vardır: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8

19

Performansa ihtiyacınız varsa, her arama yaptığınızda dizinizin tamamının üzerinden geçmek istemezsiniz.

Bu durumda, o dizinin dizinini temsil eden ilişkilendirilebilir bir dizi (karma tablosu veya sözlük) oluşturabilirsiniz. Yani her dizi öğesini dizideki dizinine eşler:

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
}

O zaman bu şekilde kullanabilirsiniz:

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

Üyeliği şöyle test edin:

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

Veya:

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

Test edilen değerde veya dizi değerlerinde boşluk olsa bile bu çözümün doğru şeyi yaptığını unutmayın.

Bonus olarak, dizideki değerin dizinini şu şekilde de alırsınız:

echo "<< ${myarray_index[$member]} >> is the index of $member"

İlişkilendirilebilir bir dizi kullanmanız gerektiği fikri için +1. Ben make_indexdolaylı kod nedeniyle biraz daha yapmacık olduğunu düşünüyorum ; çok daha basit bir kodla sabit bir dizi adı kullanmış olabilirsiniz.
musiphil

17

Genellikle sadece kullanın:

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

sıfır olmayan değer bir eşleşmenin bulunduğunu gösterir.


Doğru, bu kesinlikle en kolay çözüm - bence cevap olarak işaretlenmelidir. En azından benim oyumu al! [:
ToVine

2
Benzer iğneler için işe yaramaz. Örneğin,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan

1
Çok doğru. herhangi bir elemanda bulunmayan bir sınırlayıcıyla birleştirmek ve iğneye eklemek buna yardımcı olacaktır. Belki böyle bir şey ... (denenmemiş)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti

2
Grep -x kullanmak yanlış pozitifleri önler: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher

Belki inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)de -x, grep'in tüm giriş dizesini denemesine ve eşleştirmesine neden olur
MI Wright

17

Fonksiyonu olmayan bir başka astar:

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

Boşluklarla ilgili bilgi için @Qwerty'e teşekkürler!

karşılık gelen fonksiyon:

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

misal:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"

1
Neden burada bir alt kabuğa ihtiyacımız var?
codeforester

1
@codeforester bu eski ... ama yazıldığı gibi, ondan kurtulmak için ona ihtiyacınız var, bu ne exit 0yapar (bulunursa en kısa sürede durur).
estani

Bir astar sonu olmalıdır || echo not foundyerine || not foundveya kabuk adında bir komut çalıştırmak için çalışacağız değil argüman ile tespit istenen değer dizide değilse.
zoke

11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Artık boş dizileri doğru şekilde işler.


Bunun @ patrik'in cevabından farkı nedir? Gördüğüm tek fark "$e" = "$1"(yerine "$e" == "$1") bir hataya benziyor.
CivFan

1
O değil. @ patrik o zamanki yorumumu orijinal cevabında birleştirdi (yama # 4). Not: "e" == "$1"sözdizimsel olarak daha nettir.
Yann

@CivFan Şu anki biçiminde, zarif $ {@: 2} ve kendi kendini belgeleyen 1 $ nedeniyle patrik yanıtından daha kısadır. [[]] İçinde alıntı yapmanın gerekli olmadığını da ekleyeceğim.
Hontvári Levente

9

İşte küçük bir katkı:

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

Not: Bu şekilde durum "iki kelime" ayırt edilmez, ancak bu soruda gerekli değildir.


Bu bana çok yardımcı oldu. Teşekkürler!
Ed Manet

Soru açıkça doğru cevap vermek zorunda olduğunu söylemedi ama bence bu soru örtük ... Dizi "iki" değerini içermiyor.
tetsujin

Yukarıda 'rd' için bir eşleşme bildirilecektir.
Noel Yap

6

Kesin bir eşleşme elde etmek için tüm dizi üzerinde yinelenmeye değer olup olmadığını görmek için hızlı ve kirli bir test yapmak istiyorsanız, Bash, dizileri skaler gibi tedavi edebilir. Skalerde bir eşleşme olup olmadığını test edin, eğer hiç değilse döngüyü atlamak zaman kazandırır. Açıkçası yanlış pozitifler alabilirsiniz.

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

Bu, "Denetleniyor" ve "Eşleştir" çıktısını verir. Bununla array=(word "two words" something)birlikte sadece "Kontrol" çıktısı alır. İle array=(word "two widgets" something)hiçbir çıkış olacaktır.


Neden sadece wordsher bir ^words$dizeyi tek tek kontrol etme gereğini tamamen ortadan kaldıran, yalnızca tüm dizeyle eşleşen bir normal ifade ile değiştirilmesiniz ?
Dejay Clayton

@DejayClayton: Çünkü tüm diziyi aynı anda bir skalermiş pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]gibi kontrol ettiği için asla eşleşmeyecek . Cevabımdaki münferit kontroller, sadece kaba maça dayanarak devam etmek için bir neden varsa yapılmalıdır.
sonraki duyuruya kadar duraklatıldı.

Ah, ne yapmaya çalıştığını görüyorum. Daha performanslı ve güvenli bir değişken cevap önerdim.
Dejay Clayton

6

Bu benim için çalışıyor:

# 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
}

Örnek çağrı:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi

5
a=(b c d)

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

İsterseniz eşdeğer uzun seçenekleri kullanabilirsiniz:

--fixed-strings --quiet --line-regexp --null-data

1
Boş veri olmadığı için bu, Mac'teki BSD-grep ile çalışmaz. :(
Will

4

Borçlanma Dennis Williamson 'ın cevabı şu çözüm birleştirir diziler, kabuk güvenli alıntı ve düzenli ifadeler ihtiyacını önlemek için: döngüler üzerinde yineleme; boruların veya diğer alt işlemlerin kullanılması; veya bash olmayan yardımcı programların kullanılması.

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

Yukarıdaki kod, dizi içeriğinin sınırlı bir sürümüyle eşleşmek için Bash normal ifadelerini kullanarak çalışır. Sıralı ifade eşleşmesinin, dizideki akıllı değer kombinasyonları tarafından aldatılmamasını sağlamak için altı önemli adım vardır:

  1. Bash'in yerleşik kodunu kullanarak karşılaştırma dizesini oluşturun printf kabuk alıntısını%q . Kabuk alıntılama, özel karakterlerin ters eğik çizgi ile kaçarak "kabuk güvenli" olmasını sağlar\ .
  2. Değer sınırlayıcı olarak kullanılacak özel bir karakter seçin. Sınırlayıcı, kullanıldığında kaçacak özel karakterlerden biri OLMALIDIR %q; dizi içindeki değerlerin normal ifade eşleşmesini kandırmak için akıllıca oluşturulamayacağını garanti etmenin tek yolu budur. Virgül seçiyorum, çünkü bu karakter, başka türlü beklenmedik bir şekilde değerlendirildiğinde veya yanlış kullanıldığında en güvenli olanı.
  3. Sınırlayıcı olarak hizmet etmek için özel karakterin iki örneğini kullanarak tüm dizi öğelerini tek bir dizede birleştirin . Örnek olarak virgül kullanarak,,%q argüman olarak kullandım printf. Bu önemlidir, çünkü özel karakterin iki örneği yalnızca sınırlayıcı olarak göründüklerinde yan yana görünebilir; özel karakterin diğer tüm örnekleri kaçacaktır.
  4. Dizinin son öğesiyle eşleşmeye izin vermek için dizeye sınırlayıcının sondaki iki örneğini ekleyin. Böylece, yerine karşı karşılaştırma ${array_str}, karşı karşılaştırmak${array_str},, .
  5. Aradığınız hedef dize, bir kullanıcı değişkeni tarafından sağlanır ise, mutlaka , özel karakterin tüm örneklerinden ters eğik çizgi ile kaçmanız . Aksi takdirde, düzenli ifade eşleşmesi akıllıca hazırlanmış dizi öğeleri tarafından aldanmaya açık hale gelir.
  6. Dizeye karşı Bash normal ifade eşleşmesi gerçekleştirin.

Çok zeki. Çoğu potansiyel sorunun önlendiğini görebiliyorum, ancak herhangi bir köşe vakası olup olmadığını test etmek istiyorum. Ayrıca, 5. nokta ile ilgili bir işlem örneği görmek istiyorum printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]].
sonraki duyuruya kadar duraklatıldı.

case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino

3

@ Ghostdog74'ün casedizinin belirli bir değer içerdiğini kontrol etmek için mantık kullanma konusundaki cevabına küçük bir ekleme :

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

Veya extglobseçenek açıkken, bunu şu şekilde yapabilirsiniz:

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

Ayrıca bunu ifade ile yapabiliriz if:

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

2

verilen:

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

sonra basit bir kontrol:

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

nerede

c is element separator
p is regex pattern

(İfadeyi doğrudan [[]] içinde kullanmak yerine p'yi ayrı olarak atamanın nedeni bash 4 için uyumluluğu korumaktır)


Burada "basit" kelimesini kullanmayı seviyorum ... 😂
Christian

2

Burada sunulan fikirlerden birkaçını birleştirerek, tam kelime eşleşmelerini yapan döngüler olmadan zarif bir ifşa yapabilirsiniz .

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

Bu tetiklenmeyecek wordveya valsadece tam kelime eşleşmeleri olacaktır. Her dizi değeri birden çok kelime içeriyorsa kesilir.


1

Genellikle bu tür yardımcı programları, değişken değeri yerine değişken adı üzerinde çalışacak şekilde yazarım, çünkü bash değişkenleri referans olarak geçemez.

İşte dizinin adıyla çalışan bir sürüm:

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
}

Bununla soru örneği şöyle olur:

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

vb.


Birisi bir if içinde kullanılan bunun bir örneğini, özellikle diziye nasıl geçirdiğinizi gönderebilir. Ben bir argüman params bir dizi olarak ele alındığında betiğe bir argüman geçti olup olmadığını kontrol etmeye çalışıyorum, ama çalışmak istemiyor. params = ("$ @") check = array_con $ [params} 'SKIPDIRCHECK' ise [[$ {check} == 1]]; o zaman ... Ama betiği argüman olarak 'asas' ile çalıştırırken asas: command found komutunu söylemeye devam ediyor. : /
Steve Childs

1

kullanılması grepveprintf

Her dizi üyesini yeni bir satırda, ardından grepsatırlarda biçimlendirin .

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

Bunun, sınırlayıcılar ve boşluklarla ilgili bir sorunu olmadığını unutmayın.


1

'Grep' ve döngüler olmadan tek satır kontrol

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

Bu yaklaşım, ne grepdöngüler gibi ne harici araçlar kullanır .

Burada ne olur:

  • dizede biten dizideki öğemizi bulmak için bir joker karakter alt dizesi eşleştiricisi kullanırız;
  • arama öğemizi bir çift sınırlayıcı arasına alarak olası yanlış pozitifleri kestik;
  • güvenli tarafta olmak için sınırlayıcı olarak yazdırılamayan bir karakter kullanıyoruz;
  • IFSdeğişken değerinin geçici olarak değiştirilmesiyle dizi birleştirmede de kullanılan sınırlayıcımızı elde ederiz ;
  • IFSbir alt kabukta (bir çift parantez içinde) koşullu ifademizi değerlendirerek bu değer değişimini geçici hale getiririz

Dlm'yi ortadan kaldırın. IFS'yi doğrudan kullanın.
Robin A. Meade

Bu en iyi cevap. Çok beğendim, bu tekniği kullanarak bir fonksiyon yazdım .
Robin A. Meade

1

Parametre genişletmeyi kullanma:

$ {parametre: + word} Parametre null veya unset ise, hiçbir şey değiştirilmez, aksi takdirde kelimenin genişletilmesi değiştirilir.

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

${myarray[hello]:+_}birlik diziler için harika çalışır, ancak normal dizinlenmiş diziler için geçerli değildir. Soru, arayışta bir değer bulmak, ilişkilendirilebilir bir dizinin anahtarının var olup olmadığını kontrol etmemekle ilgilidir.
Eric

0

Yanıt verdikten sonra, özellikle beğendiğim başka bir cevap okudum, ancak kusurlu ve indirgenmemişti. İlham aldım ve burada uygulanabilir olduğunu gördüğüm iki yeni yaklaşım var.

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

grepve kullanarak printf:

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

kullanarak for:

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

Not_found sonuçları için ekleyin || <run_your_if_notfound_command_here>


0

İşte bunu benim almam.

Bunu önlemek için döngü için bir bash kullanmak istemiyorum, bu çalıştırmak için zaman alır gibi. Bir şeyin döngü yapması gerekiyorsa, bir kabuk betiğinden daha düşük bir dilde yazılmış bir şey olsun.

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

Bu _arr, indeksleri giriş dizisinin değerlerinden türetilen geçici bir ilişkilendirilebilir dizi oluşturarak çalışır . (İlişkilendirilebilir dizilerin bash 4 ve üzeri sürümlerde bulunduğunu unutmayın, bu nedenle bu işlev bash'ın önceki sürümlerinde çalışmaz.) Boşlukta $IFSsözcük bölünmesini önlemek için ayarladık .

İşlev, açık döngüler içermese de, doldurmak için girdi dizisi içinden dahili olarak bash sağlar printf. Printf formatı %q, giriş verilerinin, dizi anahtarları olarak güvenle kullanılabilecek şekilde kaçmasını sağlamak için kullanılır.

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

Bu işlevin kullandığı her şeyin bash için yerleşik olduğunu unutmayın, bu nedenle komut genişletmesinde bile sizi aşağı sürükleyen harici borular yoktur.

Ve eğer kullanmaktan hoşlanmıyorsanız eval... iyi, başka bir yaklaşım kullanmakta özgürsünüz. :-)


Dizi köşeli parantez içeriyorsa ne olur?
gniourf_gniourf

@gniourf_gniourf - köşeli parantez dengeli ise iyi görünüyor, ancak diziniz dengesiz köşeli parantez içeren değerler içeriyorsa bir sorun olduğunu görebiliyorum. Bu durumda evalcevabın sonunda talimatı çağırırdım . :)
ghoti

Sevmediğimden değil eval(ağlayan çoğu insanın aksine eval, çoğunlukla onun hakkında neyin kötü olduğunu anlamadan , ona karşı hiçbir şeyim yok ). Sadece emriniz bozuldu. Belki %qyerine %sdaha iyi olurdu.
gniourf_gniourf

1
@gniourf_gniourf: Sadece "başka bir yaklaşım" bitini kastetmiştim (ve kesinlikle seninle beraberim eval, açıkçası), ama kesinlikle haklısın, %qgörebildiğim başka bir şeyi bozmadan yardım ediyor gibi görünüyor. (% Q'nun köşeli parantezlerden de kaçacağının farkında değildim.) Gördüğüm ve giderdiğim bir diğer konu boşlukla ilgili. Bununla birlikte a=(one "two " three), Keegan'ın sorununa benzer: sadece array_contains a "two "yanlış bir negatif almakla kalmadı , aynı array_contains a twozamanda yanlış bir pozitif aldı. Ayarlayarak düzeltmek için yeterince kolay IFS.
ghoti

Boşluklarla ilgili olarak, tırnak işaretleri eksik olduğu için değil mi? ayrıca glob karakterlerle kırılıyor. Bunun yerine bunu istediğinizi düşünüyorum: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )ve local IFS=. Bash ilişkilendirilebilir bir dizide boş bir anahtar oluşturmayı reddedeceğinden, dizideki boş alanlarla ilgili hala bir sorun var. Bunu düzeltmenin hızlı bir hacky yolu kukla bir karakterin başına gelmektir, örneğin x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )ve return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf

-1

Zaten önerilen düzenli ifadeler tekniğimin sürümü:

values=(foo bar)
requestedValue=bar

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

Burada olan, desteklenen değerlerin tüm dizisini kelimelere genişletmeniz ve her birine "X-" gibi belirli bir dize eklemeniz ve bunları istenen değere yapmanızdır. Bu gerçekten dizide yer alıyorsa, sonuçta elde edilen dize, sonuçta ortaya çıkan belirteçlerden biriyle eşleşir veya tam tersi hiçbiri olmaz. İkinci durumda || operatör tetiklenir ve desteklenmeyen bir değerle uğraştığınızı bilirsiniz. Tüm bunlardan önce, istenen değer, standart kabuk dizgisi manipülasyonu yoluyla tüm önde gelen ve arkadaki boşluklardan çıkarılır.

Temiz ve zarif, inanıyorum, ancak desteklenen değerler diziniz özellikle büyükse ne kadar performans olabileceğinden emin değilim.


-1

İşte bu sorunu benim ele almam. İşte kısa versiyon:

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

Ve bence uzun versiyon, gözler için çok daha kolay.

# 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$"
}

Örnekler:

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

Bu kadar uzun zamandır bash'ı kullanmıyorum, cevapları anlamakta zorlandığım, hatta kendim yazdım :) Bu sorunun tüm bu süreden sonra hala faaliyet gösterdiğine inanamıyorum :)
Paolo Tedesco

Ne olmuş test_arr=("hello" "world" "two words")?
Qwerty

-1

Bir kimliğin başka bir komut dosyası / komut tarafından oluşturulan kimlikler listesinde yer alıp almadığını kontrol etmek zorunda kaldım. Benim için çalıştı:

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

Ayrıca şöyle kısaltabilir / sıkıştırabilirsiniz:

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

Benim durumumda, kimlik listesi için bazı JSON filtrelemek için jq çalıştırıyordum ve daha sonra kimliğimin bu listede olup olmadığını kontrol etmek zorunda kaldım ve bu benim için en iyi çalıştı. Elle oluşturulan tür dizileri LIST=("1" "2" "4")için değil, satırsonu ayrılmış komut dosyası çıktısı için çalışır.


Not: nispeten yeni olduğum için bir cevap yorumlayamadı ...


-2

Aşağıdaki kod, belirli bir değerin dizide olup olmadığını kontrol eder ve sıfır temelli ofsetini döndürür:

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

Eşleşme tüm değerlerde yapılır, bu nedenle VALUE = "three" ayarı eşleşmez.


-2

Tekrarlamak istemiyorsanız, bu araştırmaya değer olabilir:

#!/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

Snippet uyarlandı: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Bence oldukça zekice.

EDIT: Muhtemelen şunları yapabilirsiniz:

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

Ancak ikincisi sadece dizi benzersiz değerler içeriyorsa çalışır. "143" de 1'i aramak yanlış pozitif, methinks verecektir.


-2

Biraz geç, ama bunu kullanabilirsiniz:

#!/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

-2

Ben sadece zsh ile çalıştığı ortaya çıktı, ama genel yaklaşım güzel olduğunu düşünüyorum.

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

Deseni her elemandan, sadece onunla başlar ${arr[@]/#pattern/}veya ${arr[@]/%pattern/}onunla biterse çıkarırsınız . Bu iki oyuncu değişikliği bash'de çalışır, ancak ikisi de aynı anda${arr[@]/#%pattern/} bash'da çalışır sadece zsh'de çalışır.

Değiştirilen dizi orijinaline eşitse, öğeyi içermez.

Düzenle:

Bu bash'da çalışır:

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

İkame işleminden sonra her iki dizinin uzunluğunu karşılaştırır. Dizi, öğeyi içeriyorsa, ikame işlemi onu tamamen silecek ve sayı farklı olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.