Проверить, находится ли элемент в массиве в bash

17

Есть хороший способ проверить, есть ли в массиве элемент в bash (лучше, чем циклически проходить)?

В качестве альтернативы, есть ли другой способ проверить, равно ли число или строка любому из набора предопределенных констант?

Tgr
источник

Ответы:

24

В Bash 4 вы можете использовать ассоциативные массивы:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

Для первоначальной настройки массива вы также можете выполнить прямые назначения:

array[foo]=1
array[bar]=1
# etc.

или так:

array=([foo]=1 [bar]=1 [baz]=1)
Приостановлено до дальнейшего уведомления.
источник
На самом деле, тест [[]] не работает, если значение пустое. Например, "array ['test'] = ''". В этом случае ключ 'test' существует, и вы можете увидеть его в списке с $ {! Array [@]}, но "[[$ {array ['test']}]]; echo $?" Отголоски 1, а не 0.
Харидсв
1
${array[$test1]}прост, но есть проблема: он не будет работать, если вы используете set -uв своих скриптах (что рекомендуется), так как вы получите «несвязанную переменную».
Tokland
@tokland: кто это рекомендует? Я конечно не
Приостановлено до дальнейшего уведомления.
@DennisWilliamson: Хорошо, некоторые люди рекомендуют это, но я думаю, что было бы неплохо иметь решение, которое работает независимо от значения этих флагов.
Tokland
10

Это старый вопрос, но я думаю, что самое простое решение еще не появилось test ${array[key]+_}. Пример:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

Выходы:

a is set
b is set

Чтобы увидеть, как это работает, проверьте это .

tokland
источник
2
Информационное руководство рекомендует использовать его, envчтобы избежать двусмысленности в псевдонимах, прогах и других функциях, которые могли принять название «тест». Как и выше env test ${xs[a]+_} && echo "a is set". Вы также можете получить эту функциональность, используя двойные скобки, тот же трюк и проверка на [[ ! -z "${xs[b]+_}" ]] && echo "b is set"
ноль
Также вы можете использовать еще проще[[ ${xs[b]+set} ]]
Арне Л.
5

Существует способ проверить, существует ли элемент ассоциативного массива (не установлен), это отличается от пустого:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

Тогда используйте это:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Диего Ф. Дуран
источник
просто примечание: объявлять -A не работает на bash 3.2.39 (debian lenny), но работает на bash 4.1.5 (debian squeeze)
Павел Полевич,
Ассоциативные массивы были введены в Bash 4.
Диего Ф. Дуран
1
обратите внимание, что if ! some_check then return 1= some_check. Итак: isNotSet() { [[ ... ]] }. Проверьте мое решение ниже, вы можете сделать это в простой проверке.
Tokland
3

Вы можете увидеть, присутствует ли запись, отправив содержимое массива в grep.

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

Вы также можете получить индекс записи с помощью grep -n, который возвращает номер строки совпадения (не забудьте вычесть 1, чтобы получить индекс, начинающийся с нуля). Это будет достаточно быстро, за исключением очень больших массивов.

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

объяснение:

  • $( ... ) то же самое, что использование обратных галочек для захвата вывода команды в переменную
  • printf выводит mydata по одному элементу в строке
  • (все необходимые кавычки, @вместо того, чтобы вместо *. этого избегать разделения "привет мира" на 2 строки)
  • grepищет точную строку: ^и $соответствует началу и концу строки
  • grep -n возвращает строку № в виде 4: привет мир
  • grep -m 1 находит только первое совпадение
  • cut извлекает только номер строки
  • вычтите 1 из возвращенного номера строки.

Конечно, вы можете сложить вычитание в команду. Но тогда проверьте -1 на отсутствие:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) делает целочисленную арифметику
Kane
источник
1

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

Вот один простой вариант, это правильно сказал бы, что "Super User"существует в массиве. Но это также сказал бы, что "uper Use"находится в массиве.

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

Проблема в том, что нет простого способа добавить якоря (о которых я могу думать), кроме циклического перемещения по массиву. Если вы не можете добавить их, прежде чем поместить их в массив ...

Nifle
источник
Это хорошее решение, когда константы буквенно-цифровые, хотя (с grep "\b$FINDME\b"). Вероятно, может работать с не алфавитно-цифровыми константами, которые не имеют пробелов, с "(^| )$FINDME(\$| )"(или что-то в этом роде ... Мне никогда не удавалось узнать, что использует regexp grep.)
Tgr
1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi
Конг Нгуен
источник
1
Можете ли вы уточнить, почему вы предлагаете этот подход? ОП спросил, есть ли способ сделать это без циклического перебора массива , что вы и делаете in_array. Приветствия
bertieb
Ну, по крайней мере, этот цикл заключен в функцию, что может быть достаточно для многих случаев (с небольшими наборами данных) и не требует bash 4+. Вероятно, ${ARRAY[@]}следует использовать.
Тобиас