it-roy-ru.com

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

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

308
Paolo Tedesco

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

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

8
bta

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

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
312
patrik

Преимущество этого подхода состоит в том, что нет необходимости циклически проходить по всем элементам (по крайней мере, явно). Но так как 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's there"
fi
294
Keegan
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
57
ghostdog74
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
35
Scott

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

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

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"
17
LeoRochael

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

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

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

15
Sean DiSanti

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

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo found || 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
estani

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

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

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

10
hornetbzz

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

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
JellicleCat
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

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

9
Yann

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

5
Dennis Williamson
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
5
Steven Penny

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

# 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
4
Chris Prince

дано :

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)

3
Beorn Harris

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

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

2
Qwerty

Заимствуя у Денниса Уильямсона 's answer , следующее решение объединяет массивы, безопасное цитирование Shell и регулярные выражения, чтобы избежать необходимости: перебирать циклы; использование труб или других подпроцессов; или используя утилиты не 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 Shell-quoting, %q. Заключение в кавычки гарантирует, что специальные символы станут «безопасными для оболочки» благодаря экранированию с обратной косой чертой \.
  2. Выберите специальный символ, который будет служить разделителем значений. Разделитель ДОЛЖЕН быть одним из специальных символов, которые будут экранированы при использовании %q; это единственный способ гарантировать, что значения в массиве не могут быть построены умными способами, чтобы обмануть совпадение регулярного выражения. Я выбираю запятую ,, потому что этот символ самый безопасный, когда eval'd или неправильно используется иным неожиданным образом.
  3. Объедините все элементы массива в одну строку, используя two экземпляры специального символа в качестве разделителя. Используя запятую в качестве примера, я использовал ,,%q в качестве аргумента printf. Это важно, потому что два экземпляра специального символа могут появляться рядом друг с другом, только когда они появляются в качестве разделителя; все другие экземпляры специального символа будут экранированы.
  4. Добавьте два завершающих экземпляра разделителя к строке, чтобы разрешить совпадения с последним элементом массива. Таким образом, вместо сравнения с ${array_str}, сравните с ${array_str},,.
  5. Если искомая целевая строка указана в пользовательской переменной, вы должны экранировать все вхождения специального символа обратной косой чертой. В противном случае совпадение регулярного выражения становится уязвимым для того, чтобы его обманули искусно созданные элементы массива.
  6. Выполните совпадение регулярного выражения Bash со строкой.
2
Dejay Clayton

Я обычно пишу такие утилиты для работы с именем переменной, а не со значением переменной, главным образом потому, что 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"

и т.п.

2
Barry Kelly

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

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>

1
Qwerty

Вот мой взгляд на это.

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

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

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

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

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

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

1
ghoti

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

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

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

1
Ecker00

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

# 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 .: не могу прокомментировать ответ, потому что я относительно новый ...

0
E. Körner

Небольшое дополнение к ответу @ 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
0
Aleksandr Podkutin

Вот мой взгляд на эту проблему. Вот короткая версия:

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
0
robert