Проверьте, содержит ли массив Bash значение

В 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

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

Ответ 1

Существует пример кода, который показывает, как заменить подстроку из массива. Вы можете сделать копию массива и попытаться удалить целевое значение из копии. Если копия и оригинал различаются, тогда целевое значение существует в исходной строке.

Простое (но потенциально более трудоемкое) решение состоит в том, чтобы просто перебирать весь массив и проверять каждый элемент по отдельности. Это то, что я обычно делаю, потому что его легко реализовать, и вы можете обернуть его в функцию (см. эту информацию о передаче массива в функцию).

Ответ 2

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

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

Ответ 3

Этот подход имеет то преимущество, что ему не нужно перебирать все элементы (по крайней мере, не явно). Но так как array_to_string_internal() в array.c все еще обходит элементы массива и объединяет их в строку, это, вероятно, не более эффективно, чем предлагаемые решения для циклирования, но это более читаемо.

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

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

Обратите внимание, что в тех случаях, когда значение, которое вы ищете, является одним из слов в элементе массива с пробелами, оно дает ложные срабатывания. Например

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

Регулярное выражение будет видеть Jack как находящееся в массиве, даже если это не так. Поэтому вам нужно будет изменить IFS и символы разделителя в вашем регулярном выражении, если вы хотите использовать это решение, например

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

value="Jack Smith"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "yep, it there"
fi

Ответ 4

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

Ответ 5

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

Ответ 6

Если вам нужна производительность, вы не хотите перебирать весь массив во время поиска.

В этом случае вы можете создать ассоциативный массив (хеш-таблицу или словарь), который представляет индекс этого массива. То есть он сопоставляет каждый элемент массива в свой индекс в массиве:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("[email protected]")
  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"

Ответ 7

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

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

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

Ответ 8

Однолинейное решение

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 чтобы он не печатал совпадения; это просто будет относиться к существованию совпадения как к "правде".

Ответ 9

Еще один лайнер без функции:

(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 "[email protected]"; do [[ "$e" == "$word" ]] && return 0; done
}

пример:

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

Ответ 10

Вот небольшой вклад:

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

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

Ответ 14

Это работает для меня:

# 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

Ответ 15

:

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)

Ответ 16

Обычно я пишу эти утилиты для работы с именем переменной, а не с переменной, в первую очередь потому, что 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"

и др.

Ответ 17

Использование 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

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

Ответ 18

Объединяя несколько представленных здесь идей, вы можете сделать изящный, если statment без циклов, который выполняет точное совпадение слов.

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

Это не будет срабатывать на word или val, только совпадение всего слова. Он будет разбит, если каждое значение массива содержит несколько слов.

Ответ 19

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

  • Построить строку сравнения с помощью Bash встроенного printf shell-quoting, %q. Оболочка кавычек гарантирует, что специальные символы станут "безошибочными", если их экранировать с помощью обратного слэша \.
  • Выберите специальный символ, который будет использоваться в качестве разделителя значений. Разделитель должен быть одним из специальных символов, который будет экранирован при использовании %q; что единственный способ гарантировать, что значения внутри массива не могут быть построены умными способами, чтобы обмануть соответствие регулярного выражения. Я выбираю запятую ,, потому что этот символ является самым безопасным, когда eval'd или неправильно используется другим способом.
  • Объединить все элементы массива в одну строку, используя два экземпляра специального символа, которые будут служить разделителем. Используя запятую в качестве примера, я использовал ,,%q как аргумент printf. Это важно, потому что два экземпляра специального символа могут появляться только рядом друг с другом, когда они отображаются как разделитель; все другие экземпляры специального символа будут экранированы.
  • Добавьте в строку два завершающих экземпляра разделителя, чтобы разрешить совпадения с последним элементом массива. Таким образом, вместо сравнения с ${array_str} сравните с ${array_str},,.
  • Если целевая строка, которую вы ищете, предоставляется пользовательской переменной, вы должны избегать всех экземпляров специального символа с обратным слэшем. В противном случае соответствие регулярного выражения становится уязвимым для того, чтобы быть обманутым умными элементами массива.
  • Выполните сопоставление регулярных выражений Bash со строкой.

Ответ 20

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

array=("word" "two words") # let 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>

Ответ 21

Здесь я беру на себя это.

Я бы предпочел не использовать цикл bash for, если я могу его избежать, поскольку для этого требуется время. Если что-то должно зацикливаться, пусть это будет что-то, написанное на языке более низкого уровня, чем оболочка script.

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... ну, вы можете использовать другой подход.: -)

Ответ 22

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

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

Или с extglob опцией 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

Ответ 23

Вот мой вопрос по этой проблеме. Вот короткая версия:

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

Ответ 24

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

# 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 для списка идентификаторов и должен был позже проверить, был ли мой ID в этом списке, и это сработало лучше для меня. Он не будет работать для созданных вручную массивов типа LIST=("1" "2" "4") но для вывода с новой строки.


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

Ответ 25

Этот ответ основан на ответе Кигана и вдохновлен ответом Деджея Клейтона. Принцип состоит в том, чтобы разграничить элементы массива, а затем выполнить поиск первого подходящего слова. Строка, возвращаемая функцией, указывает распознанный шаблон, более того, код выхода указывает результат.

#!/bin/bash

template=('hello, stack' one 'two words' words last)

# The following function indicates if an element is a member of an array.

# The function compares each element of an array against a common word
# (second argument) using a predefined regular expression: each element
# of the array is explicitly delimited with <> to be recognized in the string.

member()
{
    local -n array=$1; local element="$2"; local string

    # may match a specific word in a multi-words string
    local regex="[^[:alpha:]]($element)[^[:alpha:]]"

    # alternatively, may match the whole string for an element
    # local regex="<($element)>"

    # after the expansion, each array element is surrounded with <>
    printf -v string "<%s>" "${array[@]}"

    [[ "$string" =~ $regex ]]

    local exit_code=$?

    # "${BASH_REMATCH[0]}" is more convenient as data
    printf "pattern='%s'\n" "${BASH_REMATCH[1]}"

    return $exit_code
}

member template "words"
member template "words" >/dev/null; echo $?

# display

# >> pattern='words'
# >> 0

Ответ 26

Следующий код проверяет, находится ли заданное значение в массиве и возвращает его смещение на основе нуля:

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" не соответствует.

Ответ 27

Это может стоить проверить, не хотите ли вы итерации:

#!/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" даст ложный результат, говорит.

Ответ 28

Несмотря на то, что здесь было несколько замечательных и полезных ответов, я не нашел того, что казалось правильным сочетанием исполнителей, кросс-платформенных и надежных; поэтому я хотел поделиться решением, которое я написал для своего кода:

#!/bin/bash

# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item ($1) is contained in an array ([email protected]).
#
# Developer note:
#    The use of a delimiter here leaves something to be desired. The ideal
#    method seems to be to use `grep` with --line-regexp and --null-data, but
#    Mac/BSD grep doesn't support --line-regexp.
function array_contains()
{
    # Extract and remove the needle from [email protected]
    local needle="$1"
    shift

    # Separates strings in the array for matching. Must be extremely-unlikely
    # to appear in the input array or the needle.
    local delimiter='#!-\8/-!#'

    # Create a string with containing every (delimited) element in the array,
    # and search it for the needle with grep in fixed-string mode.
    if printf "${delimiter}%s${delimiter}" "[email protected]" | \
        grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then
        return 0
    fi

    return 1
}

Ответ 29

Развернувшись на вышеупомянутом ответе от Sean DiSanti, я думаю, что следующее - простое и элегантное решение, которое позволяет избежать циклы над массивом и не даст ложных срабатываний из-за частичных совпадений

function is_in_array {
    local ELEMENT="${1}"
    local DELIM=","
    printf "${DELIM}%s${DELIM}" "${@:2}" | grep -q "${DELIM}${ELEMENT}${DELIM}"
}

Что можно назвать так:

$ haystack=("needle1" "needle2" "aneedle" "spaced needle")
$ is_in_array "needle" "${haystack[@]}"
$ echo $?
1
$ is_in_array "needle1" "${haystack[@]}"
$ echo $?
0

Ответ 30

Моя версия техники регулярных выражений, которая уже была предложена:

values=(foo bar)
requestedValue=bar

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

Что здесь происходит, так это то, что вы расширяете весь массив поддерживаемых значений в словах и добавляете в этом случае определенную строку "X-", причем каждый из них должен делать то же самое с запрошенным значением. Если это действительно содержится в массиве, то получившаяся строка будет в лучшем случае соответствовать одному из результирующих жетонов, или вообще нет в обратном. В последнем случае || триггеры оператора, и вы знаете, что имеете дело с неподдерживаемой стоимостью. До этого все запрашиваемое значение лишено всех начальных и конечных пробелов путем стандартного управления строкой строки.

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