Maneira mais fácil de verificar um índice ou uma chave em uma matriz?

92

Usando:

set -o nounset
  1. Ter uma matriz indexada como:

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

    Qual é o caminho mais curto para verificar se o elemento 1 está definido?
    Às vezes uso o seguinte:

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

    Gostaria de saber se existe um preferido.

  2. Como lidar com índices não consecutivos?

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

    Como verificar rapidamente o que 51já está definido, por exemplo?

  3. Como lidar com matrizes associativas?

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

    Como verificar rapidamente o que key2já é usado por exemplo?

Luca Borrione
fonte

Respostas:

134

Para verificar se o elemento está definido (aplica-se à matriz indexada e associativa)

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

Basicamente, o ${array[key]+abc}que é

  • se array[key]estiver definido, retornarabc
  • se array[key]não estiver definido, não retorne nada


Referências:

  1. Veja a Expansão de Parâmetros no manual Bash e a pequena nota

    se os dois pontos forem omitidos, o operador testa apenas a existência [do parâmetro ]

  2. Esta resposta é na verdade adaptada das respostas para esta pergunta SO: Como saber se uma string não está definida em um script bash shell ?


Uma função de wrapper:

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

Por exemplo

if ! exists key in array; then echo "No such array element"; fi 
apostar
fonte
Resolvi desta forma: if test "$ {myArray ['key_or_index'] + isset}"; em seguida, echo "sim"; senão echo "não"; fi; Parece-me a maneira mais simples e se aplica a matrizes indexadas e associativas. Obrigado
Luca Borrione
1
@doubleDown Como você usa [$ {array [key] + abc}] em uma cláusula if para fazer algo apenas se [$ {array [key] + abc}] não existir?
olala
1
Também não funciona quando você acidentalmente consulta um array enumerado como associativo.
Tomáš Zato - Reintegração de Monica
1
@duanev: Sem +abc, [ ${array[key]} ]será avaliado como falso se o elemento estiver realmente definido, mas com um valor vazio, então ele está realmente testando o valor não vazio em vez da existência da chave.
musiphil
@duanev Sem +abctambém falhou quando array[key]não está definido e set -uestá em vigor.
Ding-Yi Chen
36

Do man bash , expressões condicionais:

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

exemplo:

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

Isso mostrará que ambos foo [bar] e foo [baz] estão configurados (embora o último esteja configurado com um valor vazio) e foo [quux] não.

Vineet
fonte
1
Eu perdi isso em um rápido olhar; observe que a sintaxe típica de expansão de array não é usada.
Nathan Chappell
Com set -u, por que [[ -v "${foo[bar]}" ]]produz um erro de variável não associada se barnão existe no dicionário? Funciona bem sem o ${}; Estou acostumado a usá-lo para tudo por padrão.
bgfvdu3w
"${foo[bar]}"avalia a variável de matriz primeiro, então o [[ -vcomando testa uma variável com o nome desse valor
andysh
12

Nova resposta

Da versão 4.2 de (e mais recentes), há uma nova -vopção de testcomando embutido .

A partir da versão 4.3, este teste pode endereçar elementos de arrays.

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

Isso funciona com matrizes associativas da mesma maneira:

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

Com uma pequena diferença:
em arrays regulares, a variável entre colchetes ( [i]) é inteira, então o símbolo de dólar ( $) não é necessário, mas para array associativo, como a chave é uma palavra, $é necessário ( [$i])!

Resposta antiga para antes de V4.2

Infelizmente, o bash não faz diferença entre uma variável vazia e uma indefinida .

Mas existem algumas maneiras:

$ 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

(não responda)

E para matriz associativa, você pode usar o mesmo:

$ 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

Você poderia fazer o trabalho sem a necessidade de ferramentas externas (sem printf | grep como bash puro ) e, por que não, construir checkIfExist () como uma nova função 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

ou até mesmo criar uma nova função bash getIfExist que retorna o valor desejado e sai com um código de resultado falso se o valor desejado não existir:

$ 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
F. Hauri
fonte
Ok para votos negativos: esta resposta foi postada antes da V4.2 do bash ! Resposta editada!
F. Hauri
Não funciona bash 4.2.46. Funciona bash 4.4.12.
Irfy
@Irfy O que não funciona? -vopção de testou getIfExistfunção?
F. Hauri,
1
-vfoi adicionado ao bash-4.2 MAS o suporte para verificação de índices de array não foi adicionado até o bash-4.3.
mr.spuratic
1
@ mr.spuratic Obrigado, pelo comentário!
F. Hauri
5

testado em 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
fonte
Isso falha quando o valor da chave é uma string vazia. Como alternativa, você pode usar a +expansão do parâmetro para substituir um valor vazio por algum espaço reservado, como um sublinhado. Por exemplo , declare -A a[x]=;[[ ${a[x]} ]];echo $?imprime 1, mas declare -A a[x]=;[[ ${a[x]+_} ]];echo $?imprime 0.
nisetama
3

Que tal um -zteste e o :-operador?

Por exemplo, este script:

#!/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

Impressões:

ABC is set
DEF is set
GuyPaddock
fonte
Ótima solução compacta que responde conforme o esperado para uma string vazia
Ryan Dugan
1

Esta é a maneira mais fácil que encontrei para scripts.

<search>é a string que você deseja encontrar, ASSOC_ARRAYo nome da variável que contém sua matriz associativa.

Depende do que você deseja alcançar:

chave existe :

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

a chave não existe :

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

valor existe :

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

valor existe não :

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

Escrevi uma função para verificar se existe uma chave em uma matriz no 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
}

Exemplo

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

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

Testado com GNU bash, versão 4.1.5 (1) -release (i486-pc-linux-gnu)

Lucas Stad
fonte