¿La forma más fácil de buscar un índice o una clave en una matriz?

89

Utilizando:

set -o nounset
  1. Tener una matriz indexada como:

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

    ¿Cuál es la forma más corta de comprobar si el elemento 1 está configurado?
    A veces utilizo lo siguiente:

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

    Me gustaría saber si hay alguno preferido.

  2. ¿Cómo lidiar con índices no consecutivos?

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

    ¿Cómo comprobar rápidamente que 51ya está configurado, por ejemplo?

  3. ¿Cómo lidiar con matrices asociativas?

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

    ¿Cómo comprobar rápidamente que key2ya se utiliza por ejemplo?

Luca Borrione
fuente

Respuestas:

130

Para verificar si el elemento está configurado (se aplica tanto a la matriz indexada como a la asociativa)

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

Básicamente lo que ${array[key]+abc}hace es

  • si array[key]está configurado, regresaabc
  • si array[key]no está configurado, no devuelve nada


Referencias:

  1. Consulte Expansión de parámetros en el manual Bash y la pequeña nota

    si se omiten los dos puntos, el operador solo prueba la existencia [del parámetro ]

  2. Esta respuesta en realidad está adaptada de las respuestas para esta pregunta SO: ¿Cómo saber si una cadena no está definida en un script de shell bash ?


Una función de envoltura:

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

Por ejemplo

if ! exists key in array; then echo "No such array element"; fi 
doblar
fuente
Resolví de esta manera: si prueba "$ {myArray ['key_or_index'] + isset}"; luego repita "sí"; si no, repita "no"; fi; Me parece la forma más sencilla y se aplica a matrices indexadas y asociativas. Gracias
Luca Borrione
1
@doubleDown ¿Cómo se usa [$ {matriz [clave] + abc}] en una cláusula if para hacer algo solo si [$ {matriz [clave] + abc}] no existe?
olala
1
Tampoco funciona cuando consulta accidentalmente una matriz enumerada como asociativa.
Tomáš Zato - Restablece a Monica
1
@duanev: Sin +abc, [ ${array[key]} ]se evaluará como falso si el elemento está realmente configurado, pero con un valor vacío, por lo que en realidad está probando el valor no vacío en lugar de la existencia de la clave.
Musiphil
@duanev Sin +abctambién falló cuando array[key]no está configurado y set -ues efectivo.
Ding-Yi Chen
35

De man bash , expresiones condicionales:

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

ejemplo:

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

Esto mostrará que tanto foo [bar] como foo [baz] están configurados (aunque este último esté configurado en un valor vacío) y foo [quux] no.

Vineet
fuente
1
Lo perdí de un vistazo; observe que no se utiliza la sintaxis típica de expansión de matriz.
Nathan Chappell
Con set -u, ¿por qué [[ -v "${foo[bar]}" ]]produce un error de variable independiente si barno existe en el diccionario? Funciona bien sin el ${}; Estoy acostumbrado a usarlo para todo por defecto.
bgfvdu3w
"${foo[bar]}"evalúa primero la variable de matriz, por lo que el [[ -vcomando prueba una variable con el nombre de ese valor
andysh
10

Nueva respuesta

Desde la versión 4.2 de (y más reciente), hay una nueva -vopción para el testcomando incorporado .

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

Esto funciona con matrices asociativas de la misma manera:

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

Con una pequeña diferencia:
en las matrices regulares, la variable entre corchetes ( [i]) es un número entero, por $lo que no se requiere el símbolo de dólar ( ), pero para la matriz asociativa, ya que la clave es una palabra, $se requiere ( [$i]).

Antigua respuesta para antes de V4.2

Desafortunadamente, bash no da forma de diferenciar entre variable vacía e indefinida .

Pero hay algunas formas:

$ 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

(no dar respuesta)

Y para la matriz asociativa, podría usar lo mismo:

$ 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

Puede hacer el trabajo sin la necesidad de herramientas externas (sin printf | grep como bash puro ) y, por qué no, construir checkIfExist () como una nueva función de 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

o incluso crear una nueva función bash getIfExist que devuelva el valor deseado y salga con un código de resultado falso si el valor deseado no existe:

$ 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
fuente
Ok para votos negativos: ¡esta respuesta se publicó antes de la versión 4.2 de bash ! ¡Respuesta editada!
F.Hauri
No funciona bash 4.2.46. Funciona bash 4.4.12.
Irfy
@Irfy ¿Qué no funciona? -vopción testo getIfExistfunción?
F. Hauri
El -vno funciona en matrices en mis CentOS 7.7.1908 con la fiesta de 02/04/46. El código de su primer bloque de código se imprime not existen todos los casos bajo ese bash. (También probado en [$i]lugar de [i], sin diferencia)
Irfy
5

probado en 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
fuente
Eso falla cuando el valor de la clave es una cadena vacía. Como solución alternativa, puede utilizar la +expansión de parámetros para reemplazar un valor vacío con un marcador de posición como un guión bajo. Por ejemplo , declare -A a[x]=;[[ ${a[x]} ]];echo $?imprime 1, pero declare -A a[x]=;[[ ${a[x]+_} ]];echo $?imprime 0.
nisetama
3

¿Qué pasa con una -zprueba y el :-operador?

Por ejemplo, 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

Huellas dactilares:

ABC is set
DEF is set
GuyPaddock
fuente
Gran solución compacta que responde como se esperaba para una cadena vacía
Ryan Dugan
1

Esta es la forma más fácil que encontré para scripts.

<search>es la cadena que desea encontrar, ASSOC_ARRAYel nombre de la variable que contiene su matriz asociativa.

Depende de lo que quieras lograr:

existe la clave :

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

la clave no existe :

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

existe valor :

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

el valor no existe :

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

Escribí una función para verificar si existe una clave en una matriz en 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
}

Ejemplo

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

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

Probado con GNU bash, versión 4.1.5 (1) -release (i486-pc-linux-gnu)

Lucas Stad
fuente