Перевірте, чи містить масив Bash значення


443

У Bash, який найпростіший спосіб перевірити, чи містить масив певне значення?

Редагувати : За допомогою відповідей та коментарів після деяких тестувань я придумав таке:

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

Я не впевнений, чи це найкраще рішення, але, здається, працює.

Відповіді:


458

Цей підхід має перевагу в тому, що не потрібно перебирати всі елементи (принаймні, не явно). Але оскільки array_to_string_internal()в array.c все ще перебуває в циклі за елементами масиву і об'єднує їх у рядок, це, мабуть, не ефективніше запропонованих рішень циклічного циклу, але воно є більш читабельним.

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

Зауважте, що у випадках, коли значення, яке ви шукаєте, є одним із слів елемента масиву з пробілами, воно дає помилкові позитиви. Наприклад

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

Регекс побачить "Джек" як масив, хоча його немає. Таким чином, вам доведеться змінити IFSі розділові символи на вашому регексе, якщо ви хочете все-таки використовувати це рішення, як це

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

Це надрукує "false".

Очевидно, це також може бути використане як тестовий вислів, що дозволяє виразити його як однолінійний

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

1
Я додав пробіл на початку першого збігу значень регулярних виразів, щоб воно відповідало лише слову, а не щось, що закінчується словом. Чудово працює. Як би я не розумів, чому ти використовуєш другу умову, не вдасться, що перша спрацює одноосібно?
JStrahl

1
@AwQiruiGuo Я не впевнений, що я слідкую за цим. Ви говорите про масиви з доларовими буквами? Якщо так, то просто переконайтеся, що уникнете доларів у величині, яку ви співпадаєте із зворотними нахилами.
Кіган

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

3
Shellcheck скаржиться на це рішення, SC2199 та SC2076. Я не зміг виправити попередження, не порушивши функціональність. Будь-які думки з цього приводу, крім відключення шелчек для цієї лінії?
Алі Ессам

4
SC2076 легко виправити, просто видаліть подвійні лапки в if. Я не думаю, що існує такий спосіб уникнути SC2199 при такому підході. Вам доведеться чітко циклічно використовувати масив, як показано в інших рішеннях, або ігнорувати попередження.
Кіган

388

Нижче наведена невелика функція для досягнення цього. Рядок пошуку - перший аргумент, а решта - елементи масиву:

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

Тестовий запуск цієї функції може виглядати так:

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

5
Працює чудово! Мені просто потрібно пам'ятати , щоб передати масив , як з лапками: "${array[@]}". Інакше елементи, що містять пробіли, порушують функціональність.
Юве

26
Приємно. Я б назвав це elementIn (), оскільки він перевіряє, чи міститься перший аргумент у другому. міститьElements () звучить так, як масив пішов би першим. Для новачків, таких як я, допоможе приклад того, як використовувати функцію, яка не пише в stdout у висловленні "якщо", допоможе: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Дякую за допомогу!
GlenPeterson

5
@Bluz && конструкція булева оператора AND. Використання булевих операторів створює булеву операцію. Логічна логіка говорить, що ціле твердження може бути істинним лише в тому випадку, якщо обидва твердження до і після && оцінюються як істинні. Цей тест використовується як ярлик, який вводиться, і якщо блок.Тест оцінюється, і якщо він помилковий, немає необхідності оцінювати повернення, оскільки це не має значення для всього оператора, коли тест не вдався і тому не запускається. Якщо тест є успішним, тоді для успіху булевого оператора DOES потрібно визначити результат повернення, щоб запустити код.
петех

4
@James за умовою код успіху в bash - "0", а помилка - все> = 1. Ось чому він повертає 0 на успіх. :)
tftd

11
@Stelios shiftзміщує список аргументів на 1 ліворуч (опускаючи перший аргумент) і forбез inнеявної ітерації над аргументом.
Крістіан

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

69
Зауважте, що це не перебирає кожен елемент в масиві окремо ... натомість він просто об'єднує масив і відповідає "двом" як підрядку. Це може спричинити небажану поведінку, якщо випробовуєте, чи точне слово "два" є елементом у масиві.
MartyMacGyver

Я думав, що це буде працювати для мене в порівнянні типів файлів, але виявив, що по мірі збільшення лічильників він нараховував занадто багато значень ... boo!
Майк Q

17
неправильно! Причина: case "${myarray[@]}" in *"t"*) echo "found" ;; esacвиходи:found
Сергій Євсеєв

@MartyMacGyver, могли б ви будь ласка, подивіться на моє додаток до цього відповідь stackoverflow.com/a/52414872/1619950
Олександр Podkutin

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

Для рядків:

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

З цього приводу ви можете використовувати індексований цикл і уникати загибелі, коли елемент масиву містить IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb

@Matt: Ви повинні бути обережними, ${#}оскільки Bash підтримує рідкісні масиви.
Призупинено до подальшого повідомлення.

@ Paolo, якщо ваш масив містить пробіл, просто порівняйте його як рядок. пробіл також є рядком.
Скотт

@Paolo: Ви можете зробити цю функцію, але масиви не можна передавати як аргументи, тому вам доведеться трактувати її як глобальну.
Призупинено до подальшого повідомлення.

Денніс правий. З довідкового посібника bash: "Якщо слово подвійне цитування, ... $ {name [@]} розширює кожен елемент імені на окреме слово"
mkb

37

Однолінійне рішення

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

Пояснення

Оператор printfдрукує кожен елемент масиву в окремому рядку.

У grepвиступі використовуються спеціальні символи ^та $знаходять рядок, що містить саме такий шаблон, який задано як mypattern(ні більше, ні менше).


Використання

Щоб укласти це в if ... thenзаяву:

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

Я додав -qпрапор доgrep виразу, щоб він не друкував збіги; він просто сприйме існування відповідності як "справжній".


Приємне рішення! У GNU grep також є "--line-regexp", який міг би замінити "-P" та ^ і $ у шаблоні: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8

19

Якщо вам потрібна продуктивність, ви не хочете перебирати цикл на весь масив кожного разу при пошуку.

У цьому випадку ви можете створити асоціативний масив (хеш-таблицю або словник), який представляє індекс цього масиву. Тобто він відображає кожен елемент масиву в його індекс в масиві:

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
}

Тоді ви можете використовувати його так:

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

І тестуйте членство так:

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

Або також:

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

Зауважте, що це рішення робить правильно, навіть якщо є пробіли у тестованому значенні або у значеннях масиву.

Як бонус, ви також отримуєте індекс значення в масиві з:

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

+1 для ідеї, що вам слід використовувати асоціативний масив. Я думаю, що код для make_indexтрохи більше надуманий через непрямість; ви могли використовувати фіксовану назву масиву зі значно простішим кодом.
musiphil

17

Я зазвичай просто використовую:

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

ненульове значення означає, що відповідність знайдена.


Щоправда, це, безумовно, найпростіше рішення - на мою думку, слід зазначити відповідь. Принаймні, мою пропозицію! [:
ToVine

2
Це не буде працювати для подібних голок. Наприклад,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Кіган

1
Дуже правильно. з'єднання з роздільником, який не міститься в жодному елементі, і додавання його до голки допоможе в цьому. Можливо, щось на кшталт ... (неперевірено)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Шон ДіСанті

2
Використання grep -x дозволить уникнути помилкових позитивних результатів: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher

Можливо, просто inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)-x викликає греп, щоб спробувати узгодити весь вхідний рядок
MI Wright,

17

Ще один вкладиш без функції:

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

Дякую @Qwerty за голову щодо пробілів!

відповідна функція:

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

приклад:

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

1
Для чого нам тут потрібна нижня оболонка?
кодовий ліс

1
@codeforester це старе ... але, як це було написано, вам це потрібно для того, щоб вирватися з нього, саме це і exit 0робить (зупиняється якнайшвидше, якщо його знайдено).
estani

Кінець одного вкладиша повинен бути || echo not foundзамість, || not foundабо оболонка спробує виконати команду на ім'я не з аргументом, знайденим, якщо запитане значення не знаходиться в масиві.
зон

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

Тепер правильно обробляє порожні масиви.


Чим це відрізняється від відповіді @ Patrik? Єдина відмінність, яку я бачу - це "$e" = "$1"(замість "$e" == "$1"), яка схожа на помилку.
CivFan

1
Це не так. @ patrik з’єднав мій коментар у його оригінальній відповіді тоді (патч №4). Примітка: "e" == "$1"синтаксично чіткіше.
Янв

@CivFan У своєму теперішньому вигляді це коротше, ніж відповідь Патріка, через елегантний $ {@: 2} та самодокументування $ 1. Я додам, що цитування не потрібно в межах [[]].
Хонтварі Левенте

9

Ось невеликий внесок:

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

Зауважте: цей спосіб не відрізняє відміни "два слова", але це не потрібно в питанні.


Цей мені дуже допоміг. Дякую!
Ед Мане

У запитанні прямо не було сказано, що ви повинні дати правильну відповідь, але я думаю, що це неявно в питанні ... Масив не містить значення "два".
tetsujin

Вищезгадане повідомлення про матч 'rd'.
Ноель Яп

6

Якщо ви хочете зробити швидкий і брудний тест, щоб побачити, чи варто повторювати весь масив, щоб отримати точну відповідність, Bash може обробляти масиви як скаляри. Випробування на відповідність скаляру, якщо немає, то пропуск циклу економить час. Очевидно, ви можете отримати помилкові позитиви.

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

Це виведе "Перевірка" та "Збіг". З array=(word "two words" something)ним буде виводитися тільки "Перевірка". З array=(word "two widgets" something)виходу не буде.


Чому б просто не замінити wordsрегулярний вираз, ^words$який відповідає лише всій рядку, що повністю виключає необхідність перевірки кожного елемента окремо?
Dejay Clayton

@DejayClayton: Тому що pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]ніколи не збігатиметься, оскільки перевіряє весь масив одразу, ніби це скаляр. Індивідуальні перевірки у моїй відповіді робитимуться лише за наявності підстав для того, щоб продовжувати грунтуватися на грубій відповідності.
Призупинено до подальшого повідомлення.

А, я бачу, що ти намагаєшся зробити. Я запропонував варіантну відповідь, яка є більш ефективною та безпечною.
Dejay Clayton

6

Це працює для мене:

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

Приклад виклику:

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

Якщо ви віддаєте перевагу, ви можете використовувати еквівалентні довгі варіанти:

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

1
Це не працює з BSD-grep на Mac, оскільки немає --null-даних. :(
Will

4

Запозичуючи Dennis Williamson «s відповідь , такі рішення об'єднує в собі масиви, оболонки-безпечне цитування, і регулярні вирази , щоб уникнути необхідності: ітерація петель; використання труб або інших підпроцесів; або за допомогою не-утиліти.

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

Вищевказаний код працює, використовуючи регулярні вирази Bash, щоб відповідати раціоналізованій версії вмісту масиву. Є шість важливих кроків, щоб забезпечити, що відповідність регулярного вираження не може обдурити розумні комбінації значень у масиві:

  1. Побудувати рядок порівняння за допомогою Bash вбудованих в printfоболонці-цитували, %q. Цитування оболонок гарантуватиме, що спеціальні символи стануть "захищеними від оболонок", уникнувши зворотним нахилом \.
  2. Виберіть спеціальний символ, який буде служити роздільником значень. Розмежувач ДОЛЖЕН бути одним із спеціальних символів, які стануть уникнутими при використанні %q; це єдиний спосіб гарантувати, що значення в масиві не можуть бути побудовані розумними способами, щоб обдурити збіг регулярних виразів. Я вибираю кому, ,тому що цей символ є найбезпечнішим, коли в іншому випадку несподівано використовується або неправильно використовується.
  3. Об'єднайте всі елементи масиву в одну рядок, використовуючи два екземпляри спеціального символу, які слугуватимуть роздільником. Використовуючи козу як приклад, я використовував ,,%qяк аргумент printf. Це важливо, оскільки два екземпляри спеціального символу можуть з'являтися поруч лише тоді, коли вони відображаються як роздільник; всі інші екземпляри спеціального символу будуть уникнуті.
  4. Додайте до рядку два екземпляри відривника, щоб дозволити збіги з останнім елементом масиву. Таким чином, замість порівняння проти ${array_str}, порівняйте проти ${array_str},,.
  5. Якщо цільовий рядок, який ви шукаєте, надається користувацькою змінною, ви повинні уникати всіх екземплярів спеціального символу за допомогою зворотної косої риски. В іншому випадку відповідність регулярного вираження стає вразливою, оскільки його обманюють вміло складені елементи масиву.
  6. Виконайте відповідність регулярного вираження Bash проти рядка.

Дуже розумний. Я можу побачити, що більшість потенційних проблем запобігають, але я хотів би перевірити, чи є якісь кутові випадки. Також я хотів би побачити приклад обробки пункту 5. Щось подібне, printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]можливо.
Призупинено до подальшого повідомлення.

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

3

Невелике доповнення до відповіді @ ghostdog74 про використання caseлогіки для перевірки, чи містить масив особливе значення:

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

Або з extglobувімкненою опцією, ви можете це зробити так:

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

Також ми можемо це зробити із ifзаявою:

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

2

дано:

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

то проста перевірка:

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

де

c is element separator
p is regex pattern

(Причиною призначення p окремо, а не використання виразу безпосередньо всередині [[]] є підтримка сумісності для bash 4)


люблю вживання тут слова "простий" ... 😂
Крістіан

2

Поєднуючи декілька представлених тут ідей, ви можете зробити елегантний, якщо згорнутий без петель, що відповідає точним словам .

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

Це не призведе до wordабо val, лише, ціле слово збігається. Він порушиться, якщо кожне значення масиву містить кілька слів.


1

Я зазвичай пишу такі утиліти для роботи над ім'ям змінної, а не значенням змінної, в першу чергу тому, що bash не може передавати змінні за посиланням.

Ось версія, яка працює з назвою масиву:

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
}

З цього приклад питання стає:

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

тощо.


Чи може хтось розмістити приклад цього використовуваного в межах if, особливо як ви передаєте масив. Я намагаюся перевірити, чи був переданий аргумент сценарію, трактуючи парами як масив, але він не хоче працювати. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; потім .... Але при запуску скрипту з аргументом "asas" він продовжує говорити asas: команда не знайдена. : /
Стів Чайлдс

1

Використання grepтаprintf

Відформатуйте кожного члена масиву в новий рядок, а потім grepрядки.

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

Зауважте, що це не має проблем із деліметрами та пробілами.


1

Однорядкова перевірка без 'grep' та циклів

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

Цей підхід не використовує ані зовнішніх утиліт, grepаніж циклів.

Що відбувається тут:

  • ми використовуємо підрядник підстрокової підстановки для пошуку нашого елемента в масиві, об'єднаному в рядок;
  • ми вирізаємо можливі помилкові позитиви, додаючи наш пошуковий елемент між парою роздільників;
  • ми використовуємо недрукувальний символ як роздільник, щоб бути в безпечній стороні;
  • ми досягаємо, щоб наш роздільник був використаний і для конкатенації масиву тимчасовою заміною IFSзмінної величини;
  • ми робимо це IFSзаміщення значення тимчасовим, оцінюючи наше умовне вираження в підколонці (всередині пари в дужках)

Усуньте длм. Використовуйте IFS безпосередньо.
Робін А. Мід

Це найкраща відповідь. Мені це дуже сподобалось, я написав функцію, використовуючи цю техніку .
Робін А. Мід

1

Використання розширення параметрів:

$ {параметр: + слово} Якщо параметр є нульовим або не встановленим, нічого не замінено, інакше розширення слова замінено.

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]:+_}відмінно підходить для асоціативних масивів, але не для звичайних індексованих масивів. Питання полягає у знаходженні значення в масиві, а не в перевірці наявності ключа асоціативного масиву.
Ерік

0

Після відповіді я прочитав ще одну відповідь, яка мені особливо сподобалась, але вона була хибною і негативною. Я надихнувся, і ось два нових підходи, які я вважаю життєздатними.

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

з використанням grepта printf:

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

використовуючи for:

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

Для не знайдених результатів додайте || <run_your_if_notfound_command_here>


0

Ось мій погляд на це.

Я краще не використовую bash для циклу, якщо я можу цього уникнути, оскільки для запуску потрібен час. Якщо щось потрібно циклічно, нехай це буде щось, що було написано мовою нижчого рівня, ніж сценарій оболонки.

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

Це працює, створюючи тимчасовий асоціативний масив, _arrіндекси якого отримані зі значень вхідного масиву. (Зверніть увагу, що асоціативні масиви доступні в bash 4 і вище, тому ця функція не буде працювати в більш ранніх версіях bash.) Ми налаштовані $IFSуникати розбиття слів на пробіли.

Функція не містить явних циклів, хоча внутрішньо прошиває кроки через вхідний масив для заповнення printf. Формат printf використовує %qдля того, щоб уникнути вхідних даних таким чином, щоб їх можна було безпечно використовувати як ключі масиву.

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

Зауважте, що все, що ця функція використовує, є вбудованою для удару, тому немає зовнішніх труб, які б тягнули вас вниз, навіть при розширенні команди.

І якщо вам не подобається використовувати eval... ну, ви можете використовувати інший підхід. :-)


Що робити, якщо масив містить квадратні дужки?
gniourf_gniourf

@gniourf_gniourf - здається, це добре, якщо квадратні дужки збалансовані, але я можу бачити, що це проблема, якщо ваш масив включає значення з неврівноваженими квадратними дужками. У такому випадку я б посилався на evalінструкцію в кінці відповіді. :)
ghoti

Це не те, що мені не подобається eval(я не маю нічого проти цього, на відміну від більшості людей, які плачуть evalзло, здебільшого, не розуміючи, що в цьому є зле). Тільки що ваша команда порушена. Можливо, %qзамість того, %sщоб було краще.
gniourf_gniourf

1
@gniourf_gniourf: Я мав на увазі лише біт "іншого підходу" (і я абсолютно з тобою eval, очевидно), але ти абсолютно правий, %qздається, допомагає, не порушуючи нічого іншого, що я бачу. (Я не розумів, що% q також уникне квадратних дужок.) Ще одна проблема, яку я побачив і виправила, стосувалася пробілу. З a=(one "two " three), подібно до випуску Кігана: не тільки array_contains a "two "отримав помилковий негатив, але й array_contains a twoотримав помилковий позитив. Досить просто виправити, встановивши IFS.
ghoti

Щодо пробілів, чи не тому, що відсутні цитати? він також розривається з глобальними символами. Я думаю, що ти хочеш цього замість цього: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )і ти можеш викопати local IFS=. Проблема з порожніми полями в масиві все ще існує, оскільки Bash відмовиться створювати порожній ключ в асоціативному масиві. Швидкий хакітський спосіб виправити це - додати манекена, скажімо x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )і return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf

-1

Моя версія методики регулярних виразів, яка вже запропонована:

values=(foo bar)
requestedValue=bar

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

Тут відбувається те, що ви розширюєте весь масив підтримуваних значень у словах і додаєте до кожного з них певний рядок "X-", і робите те ж саме, що і запитуване значення. Якщо цей дійсно міститься в масиві, то результуюча рядок, щонайменше, буде відповідати одному з отриманих лексем, або взагалі жодному не навпаки. В останньому випадку || спрацьовує оператор, і ви знаєте, що маєте справу з непідтримуваним значенням. Перед цим запитуване значення позбавляється всіх провідних і кінцевих пробілів за допомогою стандартної маніпуляції з рядком оболонки.

Я вважаю, що це чисто і елегантно, хоча я не надто впевнений у тому, наскільки він ефективний, якщо масив підтримуваних значень особливо великий.


-1

Ось мій погляд на цю проблему. Ось коротка версія:

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

І довгий варіант, який, на мою думку, набагато простіший на очах.

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

Приклади:

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

Я так довго не використовую bash, коли мені важко зрозуміти відповіді, або навіть те, що я написав сам :) Я не можу повірити, що це питання все ще набуває активності після цього часу :)
Паоло Тедеско

Про що test_arr=("hello" "world" "two words")?
Qwerty

-1

У мене був випадок, що мені довелося перевірити, чи міститься ідентифікатор у списку ідентифікаторів, генерованих іншим скриптом / командою. Для мене працювали наступні:

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

Ви також можете скоротити / ущільнити його так:

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

У моєму випадку я запускав jq, щоб відфільтрувати якийсь JSON за списком ідентифікаторів, і мені довелося пізніше перевірити, чи є мій ідентифікатор у цьому списку, і це працювало найкраще для мене. Він не буде працювати для створених вручну масивів типу, LIST=("1" "2" "4")але для виводу сценарію, розділеного новим рядком.


PS .: не змогли прокоментувати відповідь, оскільки я відносно новий ...


-2

Наступний код перевіряє, чи є задане значення в масиві, і повертає його нульове зміщення:

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

Збіг проводиться за повними значеннями, тому встановлення VALUE = "три" не відповідає.


-2

Це може бути варто вивчити, якщо ви не хочете повторювати:

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

Фрагмент адаптовано з: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Я думаю, що це досить розумно.

EDIT: Можливо, ви могли просто зробити:

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

Але останній працює лише в тому випадку, якщо масив містить унікальні значення. Шукаючи 1 у "143", дасть помилкові позитиви, методи.


-2

Трохи пізно, але ви можете скористатися цим:

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

Я придумав цей, який, як виявляється, працює лише в zsh, але я думаю, загальний підхід є приємним.

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

Ви виймаєте свій візерунок з кожного елемента, лише якщо він починається ${arr[@]/#pattern/}або закінчується ${arr[@]/%pattern/}ним. Ці дві заміни працюють у bash, але обидві одночасно працюють ${arr[@]/#%pattern/}лише в zsh.

Якщо модифікований масив дорівнює оригіналу, він не містить елемента.

Редагувати:

Цей працює в bash:

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

Після підстановки вона порівнює довжину обох масивів. Обов`язково, якщо масив містить елемент, заміна повністю видалить його, а кількість буде різною.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.