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