Самый простой способ проверить индекс или ключ в массиве?

89

С помощью:

set -o nounset
  1. Имея индексированный массив, например:

    myArray=( "red" "black" "blue" )
    

    Какой самый короткий способ проверить, установлен ли элемент 1?
    Иногда использую следующее:

    test "${#myArray[@]}" -gt "1" && echo "1 exists" || echo "1 doesn't exist"
    

    Я хотел бы знать, есть ли предпочтительный вариант.

  2. Как работать с непоследовательными индексами?

    myArray=()
    myArray[12]="red"
    myArray[51]="black"
    myArray[129]="blue"
    

    Как быстро проверить, например, что 51уже установлено?

  3. Как работать с ассоциативными массивами?

    declare -A myArray
    myArray["key1"]="red"
    myArray["key2"]="black"
    myArray["key3"]="blue"
    

    Как быстро проверить, например, что key2уже используется?

Лука Боррионе
источник

Ответы:

130

Чтобы проверить, установлен ли элемент (применяется как к индексированному, так и к ассоциативному массиву)

[ ${array[key]+abc} ] && echo "exists"

В основном то, что ${array[key]+abc}делает

  • если array[key]установлено, вернутьabc
  • если array[key]не установлен, ничего не вернуть


Ссылки:

  1. См. Раздел « Расширение параметров» в руководстве Bash и небольшую заметку

    если двоеточие опущено, оператор проверяет только наличие [ параметра ]

  2. Этот ответ фактически адаптирован из ответов на этот вопрос SO: как определить, не определена ли строка в сценарии оболочки bash ?


Функция-оболочка:

exists(){
  if [ "$2" != in ]; then
    echo "Incorrect usage."
    echo "Correct usage: exists {key} in {array}"
    return
  fi   
  eval '[ ${'$3'[$1]+muahaha} ]'  
}

Например

if ! exists key in array; then echo "No such array element"; fi 
удвоить
источник
Решил так: if test "$ {myArray ['key_or_index'] + isset}"; затем повторите «да»; иначе эхо "нет"; fi; Мне кажется, что это самый простой способ, применимый к индексированным и ассоциативным массивам. Спасибо
Лука Боррионе
1
@doubleDown Как использовать [$ {array [key] + abc}] в предложении if, чтобы что-то делать, только если [$ {array [key] + abc}] не существует?
olala
1
Также не работает, когда вы случайно запрашиваете нумерованный массив как ассоциативный.
Томаш Зато - Восстановите Монику
1
@duanev: Без +abc, [ ${array[key]} ]будет оцениваться как false, если элемент действительно установлен, но с пустым значением, поэтому фактически проверяется непустота значения, а не наличие ключа.
musiphil
@duanev Без +abcтакже не работает, когда array[key]не установлен и set -uдействует.
Дин-И Чен,
35

Из man bash условные выражения:

-v varname
              True if the shell variable varname is set (has been assigned a value).

пример:

declare -A foo
foo[bar]="this is bar"
foo[baz]=""
if [[ -v "foo[bar]" ]] ; then
  echo "foo[bar] is set"
fi
if [[ -v "foo[baz]" ]] ; then
  echo "foo[baz] is set"
fi
if [[ -v "foo[quux]" ]] ; then
  echo "foo[quux] is set"
fi

Это покажет, что и foo [bar], и foo [baz] установлены (даже если последний установлен в пустое значение), а foo [quux] - нет.

Винит
источник
1
Я пропустил это с первого взгляда; обратите внимание, что типичный синтаксис расширения массива не используется.
Натан Чаппелл,
С set -u, почему [[ -v "${foo[bar]}" ]]выдает ошибку несвязанной переменной, если barее нет в словаре? Прекрасно работает без ${}; Просто привык по умолчанию для всего.
bgfvdu3w
"${foo[bar]}"сначала оценивает переменную массива, поэтому [[ -vкоманда проверяет переменную с именем этого значения
andysh
10

Новый ответ

Начиная с версии 4.2 (и новее), есть новая -vопция встроенной testкоманды.

array=([12]="red" [51]="black" [129]="blue")

for i in 10 12 30 {50..52} {128..131};do
    if [ -v array[i] ];then
        echo "Variable 'array[$i]' is defined"
    else
        echo "Variable 'array[$i]' not exist"
    fi
done
Variable 'array[10]' not exist
Variable 'array[12]' is defined
Variable 'array[30]' not exist
Variable 'array[50]' not exist
Variable 'array[51]' is defined
Variable 'array[52]' not exist
Variable 'array[128]' not exist
Variable 'array[129]' is defined
Variable 'array[130]' not exist
Variable 'array[131]' not exist

Это работает с ассоциативными массивами таким же образом:

declare -A aArray=([foo]="bar" [bar]="baz" [baz]=$'Hello world\041')

for i in alpha bar baz dummy foo test;do
    if [ -v aArray[$i] ];then
        echo "Variable 'aArray[$i]' is defined"
    else
        echo "Variable 'aArray[$i]' not exist"
    fi
done
Variable 'aArray[alpha]' not exist
Variable 'aArray[bar]' is defined
Variable 'aArray[baz]' is defined
Variable 'aArray[dummy]' not exist
Variable 'aArray[foo]' is defined
Variable 'aArray[test]' not exist

С небольшой разницей:
в обычных массивах переменная в скобках ( [i]) является целочисленной, поэтому символ доллара ( $) не требуется, но для ассоциативного массива, поскольку ключ является словом, $требуется ( [$i])!

Старый ответ для до V4.2

К сожалению, bash никак не влияет на разницу между пустой и неопределенной переменной.

Но есть несколько способов:

$ array=()
$ array[12]="red"
$ array[51]="black"
$ array[129]="blue"

$ echo ${array[@]}
red black blue

$ echo ${!array[@]}
12 51 129

$ echo "${#array[@]}"
3

$ printf "%s\n" ${!array[@]}|grep -q ^51$ && echo 51 exist
51 exist

$ printf "%s\n" ${!array[@]}|grep -q ^52$ && echo 52 exist

(не отвечаю)

И для ассоциативного массива вы можете использовать то же самое:

$ unset array
$ declare -A array
$ array["key1"]="red"
$ array["key2"]="black"
$ array["key3"]="blue"
$ echo ${array[@]}
blue black red

$ echo ${!array[@]}
key3 key2 key1

$ echo ${#array[@]}
3

$ set | grep ^array=
array=([key3]="blue" [key2]="black" [key1]="red" )

$ printf "%s\n" ${!array[@]}|grep -q ^key2$ && echo key2 exist || echo key2 not exist
key2 exist

$ printf "%s\n" ${!array[@]}|grep -q ^key5$ && echo key5 exist || echo key5 not exist
key5 not exist

Вы можете выполнить эту работу без использования внешних инструментов (без printf | grep как в чистом bash ), а почему бы и нет, создайте checkIfExist () как новую функцию bash:

$ checkIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) return 0 ;;
        * ) return 1 ;;
      esac";
}

$ checkIfExist array key2 && echo exist || echo don\'t
exist

$ checkIfExist array key5 && echo exist || echo don\'t
don't

или даже создать новую bash-функцию getIfExist, которая возвращает желаемое значение и выходит с ложным кодом результата, если желаемое значение не существует:

$ getIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) echo \${$1[$2]};return 0 ;;
        * ) return 1 ;;
      esac";
}

$ getIfExist array key1
red
$ echo $?
0

$ # now with an empty defined value
$ array["key4"]=""
$ getIfExist array key4

$ echo $?
0
$ getIfExist array key5
$ echo $?
1
Ф. Хаури
источник
Хорошо для голосов против: этот ответ был опубликован до версии 4.2 bash ! Ответ отредактирован!
Ф. Хаури,
Не работает дальше bash 4.2.46. Работает дальше bash 4.4.12.
Irfy
@Irfy Что не работает? -vвариант testили getIfExistфункция?
Ф. Хаури,
-vНе работает с массивами на моем CentOS 7.7.1908 с Баш 4.2.46. Код из вашего первого блока кода not existво всех случаях печатается под этим bash. (Также пробовал [$i]вместо [i], без разницы)
Irfy
5

протестировано в bash 4.3.39 (1) -release

declare -A fmap
fmap['foo']="boo"

key='foo'
# should echo foo is set to 'boo'
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
key='blah'
# should echo blah is unset in fmap
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
gdoubleod
источник
Это не удается, если значение ключа - пустая строка. В качестве обходного пути вы можете использовать +расширение параметра, чтобы заменить пустое значение некоторым заполнителем, например подчеркиванием. Например declare -A a[x]=;[[ ${a[x]} ]];echo $?печатает 1, а declare -A a[x]=;[[ ${a[x]+_} ]];echo $?печатает 0.
нисетама
3

А как насчет -zтеста и :-оператора?

Например, этот скрипт:

#!/usr/bin/env bash

set -e
set -u

declare -A sample

sample["ABC"]=2
sample["DEF"]=3

if [[ ! -z "${sample['ABC']:-}" ]]; then
  echo "ABC is set"
fi

if [[ ! -z "${sample['DEF']:-}" ]]; then
  echo "DEF is set"
fi

if [[ ! -z "${sample['GHI']:-}" ]]; then
  echo "GHI is set"
fi

Печать:

ABC is set
DEF is set
GuyPaddock
источник
Отличное компактное решение, которое, как и ожидалось, реагирует на пустую строку
Райан Дуган
1

Это самый простой способ, который я нашел для скриптов.

<search>- строка, которую вы хотите найти, ASSOC_ARRAYимя переменной, содержащей ваш ассоциативный массив.

В зависимости от того, чего вы хотите достичь:

ключ существует :

if grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key is present; fi

ключ существует не :

if ! grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key not present; fi

значение существует :

if grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value is present; fi

значение существует не :

if ! grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value not present; fi
sjas
источник
1

Я написал функцию, чтобы проверить, существует ли ключ в массиве в Bash:

# Check if array key exists
# Usage: array_key_exists $array_name $key
# Returns: 0 = key exists, 1 = key does NOT exist
function array_key_exists() {
    local _array_name="$1"
    local _key="$2"
    local _cmd='echo ${!'$_array_name'[@]}'
    local _array_keys=($(eval $_cmd))
    local _key_exists=$(echo " ${_array_keys[@]} " | grep " $_key " &>/dev/null; echo $?)
    [[ "$_key_exists" = "0" ]] && return 0 || return 1
}

пример

declare -A my_array
my_array['foo']="bar"

if [[ "$(array_key_exists 'my_array' 'foo'; echo $?)" = "0" ]]; then
    echo "OK"
else
    echo "ERROR"
fi

Протестировано с помощью GNU bash, версия 4.1.5 (1) -release (i486-pc-linux-gnu)

Лукас Стад
источник