Проверьте, содержит ли массив 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

Я не уверен, что это лучшее решение, но, похоже, оно работает.

Паоло Тедеско
источник

Ответы:

459

Преимущество этого подхода заключается в том, что нет необходимости перебирать все элементы (по крайней мере, явно). Но поскольку 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

Это напечатает «ложь».

Очевидно, что это также может быть использовано в качестве выражения теста, что позволяет выразить его в виде одной строки

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Киган
источник
1
Я добавил пробел в начале первого совпадения значения регулярного выражения, чтобы оно совпадало только со словом, а не с чем-то, заканчивающимся словом. Работает отлично. Как бы то ни было, я не понимаю, почему вы используете второе условие, разве первое не будет работать в одиночку?
JStrahl
1
@AwQiruiGuo Я не уверен, что следую. Вы говорите о массивах с долларовыми литералами? Если это так, просто убедитесь, что у вас нет долларов в значении, с которым вы сравниваете обратную косую черту.
Киган
10
Автор: [[ " ${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
Patrik
источник
5
Работает красиво! Мне просто нужно помнить , чтобы передать массив , как с кавычками: "${array[@]}". В противном случае элементы, содержащие пробелы, нарушат функциональность.
Юв
26
Ницца. Я бы назвал его elementIn (), потому что он проверяет, содержится ли первый аргумент во втором. containsElements () звучит так, как если бы массив шел первым. Для таких новичков, как я, может помочь пример использования функции, которая не записывает в stdout оператор «if»: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Спасибо за вашу помощь!
ГленПетерсон
5
@Bluz конструкция && является логическим оператором AND. Использование логических операторов создает логическое выражение. Логическая логика говорит, что весь оператор может быть истинным, только если оба оператора до и после && оцениваются как истинные. Это используется в качестве ярлыка для вставки, и если блок. Тест оценивается, и если ложь, нет необходимости оценивать возвращаемое значение, поскольку оно не имеет отношения ко всему утверждению после того, как тест не пройден и, следовательно, не запущен. Если проверка прошла успешно, то для успешного выполнения логического оператора требуется, чтобы результат возврата был определен.
peteches
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
ghostdog74
источник
69
Обратите внимание, что это не повторяет каждый элемент в массиве в отдельности ... вместо этого он просто объединяет массив и сопоставляет «два» в качестве подстроки. Это может вызвать нежелательное поведение, если вы проверяете, является ли точное слово «два» элементом массива.
MartyMacGyver
Я подумал, что это сработает для меня при сравнении типов файлов, но обнаружил, что по мере увеличения счетчиков он считает слишком много значений ... бу!
Майк Q
17
неправильно! Причина: case "${myarray[@]}" in *"t"*) echo "found" ;; esacвыводы:found
Сергей Евсеев
@MartyMacGyver, не могли бы вы взглянуть на мое дополнение к этому ответу stackoverflow.com/a/52414872/1619950
Александр Подкутин
46
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
Скотт
источник
Тем не менее, вы можете использовать индексированный цикл for и избежать гибели, когда элемент массива содержит 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выражению, чтобы оно не печатало совпадения; это просто будет относиться к существованию совпадения как к «правде».

JellicleCat
источник
Отличное решение! В 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"
LeoRochael
источник
+1 за идею, что вы должны использовать ассоциативный массив. Я думаю, что код для make_indexнемного более изобретательный из-за косвенности; Вы могли бы использовать фиксированное имя массива с гораздо более простым кодом.
Musiphil
17

Я обычно просто использую:

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

ненулевое значение указывает, что совпадение было найдено.

Шон ДиСанти
источник
Правда, это определенно самое простое решение - должен быть отмечен ответ на мой взгляд. По крайней мере, мой upvote! [:
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 заставляет grep пытаться сопоставить всю входную строку
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"
estani
источник
1
Зачем нам нужна подоболочка здесь?
Codeforester,
1
@codeforester это старый ... но, как было написано, он нужен для того, чтобы от него отказаться, вот что exit 0делает (останавливается как можно скорее, если найден).
Эстани
|| echo not foundВместо одного конца должен стоять конец строки, || not foundиначе оболочка попытается выполнить команду по имени not с найденным аргументом, если запрошенное значение отсутствует в массиве.
zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Теперь обрабатывает пустые массивы правильно.

Yann
источник
Чем это отличается от ответа @ patrik? Единственное отличие, которое я вижу, - это "$e" = "$1"(вместо "$e" == "$1"), который выглядит как ошибка.
CivFan
1
Не то. @ Патрик слил мой комментарий в свой первоначальный ответ (патч № 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 !"  

Примечание: этот способ не различает регистр «два слова», но в вопросе этого не требуется.

hornetbzz
источник
Этот мне очень помог. Спасибо!
Эд Мане
В вопросе явно не сказано, что вы должны были дать правильный ответ, но я думаю, что это подразумевается в вопросе ... Массив не содержит значения «два».
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$которое соответствует только всей строке, что полностью исключает необходимость проверки каждого элемента в отдельности?
Деджей Клейтон,
@DejayClayton: Потому pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]что никогда не будет совпадать, так как он проверяет весь массив сразу, как если бы он был скалярным. Индивидуальные проверки в моем ответе должны проводиться только в том случае, если есть причина продолжить на основе приблизительного соответствия.
Приостановлено до дальнейшего уведомления.
Ах, я вижу, что вы пытаетесь сделать. Я предложил вариант ответа, который был бы более производительным и безопасным.
Деджей Клейтон
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-data. :(
Будет
4

Заимствуя Dennis Williamson «s ответ , следующие решение объединяет в себе массивы, оболочки-безопасное цитирование, и регулярные выражения , чтобы избежать необходимости: итерация петель; использование труб или других подпроцессов; или используя утилиты не 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

Приведенный выше код работает с использованием регулярных выражений Bash для сопоставления со строкой версии содержимого массива. Есть шесть важных шагов, чтобы гарантировать, что совпадение с регулярным выражением не может быть одурачено умными комбинациями значений в массиве:

  1. Создайте строку сравнения с помощью встроенного в Bash printf оболочке-цитировали, %q. Заключение в кавычки гарантирует, что специальные символы станут «безопасными для оболочки», будучи экранированными с помощью обратной косой черты \.
  2. Выберите специальный символ, который будет служить разделителем значений. Разделитель ДОЛЖЕН быть одним из специальных символов, которые будут экранированы при использовании%q ; это единственный способ гарантировать, что значения в массиве не могут быть построены умными способами, чтобы обмануть совпадение регулярного выражения. Я выбираю запятую, потому что этот персонаж самый безопасный, когда eval'd или неправильно используется иначе неожиданным образом.
  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только совпадения всего слова. Он сломается, если каждое значение массива будет содержать несколько слов.

Ecker00
источник
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

Обратите внимание, что это не имеет проблем с разделителями и пробелами.

Qwerty
источник
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>

Для not_found результатов добавить || <run_your_if_notfound_command_here>

Qwerty
источник
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 чтобы избежать разбиения слов в пробелах.

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

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

Обратите внимание, что все, что использует эта функция, является встроенным в bash, поэтому нет никаких внешних каналов, тянущих вас вниз, даже в расширении команды.

И если вам не нравится eval... ну, вы можете использовать другой подход. :-)

Ghoti
источник
Что если массив содержит квадратные скобки?
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-», к каждому из них и делаете то же самое для запрошенного значения. Если этот массив действительно содержится в массиве, то результирующая строка будет максимально соответствовать одному из полученных токенов, или, наоборот, ни одному из них. В последнем случае || оператор срабатывает, и вы знаете, что имеете дело с неподдерживаемым значением. До всего этого запрошенное значение удаляется из всех начальных и конечных пробелов посредством стандартных манипуляций со строками оболочки.

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

jmpp
источник
-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 = "three" не будет соответствовать.

Свен Рике
источник
-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/ Я думаю, что это довольно умно.

РЕДАКТИРОВАТЬ: Вы могли бы просто сделать:

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

Но последний работает, только если массив содержит уникальные значения. Поиск 1 в «143» даст ложное срабатывание, метинкс.

Sigg3.net
источник
-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
Кодер-256
источник
-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[@]}" ]]
    }

После подстановки сравнивается длина обоих массивов. Очевидно, что если массив содержит элемент, подстановка полностью удалит его, и количество будет отличаться.

spelufo
источник