Prueba si el elemento está en la matriz en bash

17

¿Hay una buena manera de verificar si una matriz tiene un elemento en bash (mejor que recorrerlo)?

Alternativamente, ¿hay otra forma de verificar si un número o una cadena es igual a cualquiera de un conjunto de constantes predefinidas?

Tgr
fuente

Respuestas:

24

En Bash 4, puede usar matrices asociativas:

# 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

Para configurar la matriz inicialmente, también puede hacer asignaciones directas:

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

o de esta manera:

array=([foo]=1 [bar]=1 [baz]=1)
Pausado hasta nuevo aviso.
fuente
En realidad, la prueba [[]] no funciona en el caso de que el valor esté vacío. Por ejemplo, "array ['test'] = ''". En este caso, la clave 'prueba' existe y puede verla listada con $ {! Array [@]}, pero "[[$ {array ['prueba']}]]; echo $?" ecos 1, no 0.
haridsv
1
${array[$test1]}es simple pero tiene un problema: no funcionará si lo usa set -uen sus scripts (lo cual se recomienda), ya que obtendría "variable independiente".
tokland
@tokland: ¿Quién lo recomienda? Ciertamente no.
Pausado hasta nuevo aviso.
@DennisWilliamson: Ok, algunas personas lo recomiendan, pero creo que sería bueno tener una solución que funcione independientemente del valor de estas banderas.
tokland
10

Es una vieja pregunta, pero creo que lo que es la solución más simple no ha aparecido todavía: test ${array[key]+_}. Ejemplo:

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"

Salidas:

a is set
b is set

Para ver cómo funciona, verifique esto .

tokland
fuente
2
El manual de información le recomienda que utilice envpara evitar ambigüedades en alias, programas y otras funciones que pueden haber adoptado el nombre "prueba". Como arriba env test ${xs[a]+_} && echo "a is set". También puede obtener esta funcionalidad usando corchetes dobles, el mismo truco y luego verificando si es nulo:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski
También puedes usar el aún más simple[[ ${xs[b]+set} ]]
Arne L.
5

Hay una manera de probar si existe un elemento de una matriz asociativa (no establecido), esto es diferente de vacío:

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

Entonces úsalo:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Diego F. Durán
fuente
solo una nota: declare -A no funciona en bash 3.2.39 (debian lenny), pero funciona en bash 4.1.5 (debian squeeze)
Paweł Polewicz
Las matrices asociativas se introdujeron en Bash 4.
Diego F. Durán
1
tenga en cuenta que if ! some_check then return 1= some_check. Por lo tanto: isNotSet() { [[ ... ]] }. Verifique mi solución a continuación, puede hacerlo en una simple verificación.
tokland
3

Puede ver si una entrada está presente canalizando los contenidos de la matriz a grep.

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

También puede obtener el índice de una entrada con grep -n, que devuelve el número de línea de una coincidencia (recuerde restar 1 para obtener un índice basado en cero) Esto será razonablemente rápido, excepto para matrices muy grandes.

# 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

explicación:

  • $( ... ) es lo mismo que usar backticks para capturar la salida de un comando en una variable
  • printf Produce mydata un elemento por línea
  • (todas las citas necesarias, junto con en @lugar de *. esto, evitan dividir "hello world" en 2 líneas)
  • grepbusca una cadena exacta: ^y $coincide con el comienzo y el final de la línea
  • grep -n devuelve la línea #, en forma de 4: hola mundo
  • grep -m 1 encuentra solo el primer partido
  • cut extrae solo el número de línea
  • reste 1 del número de línea devuelto.

Por supuesto, puede doblar la resta en el comando. Pero luego prueba para -1 por falta:

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

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) hace aritmética de enteros
kane
fuente
1

No creo que pueda hacerlo correctamente sin bucle a menos que tenga muy datos limitados en la matriz.

Aquí hay una variante simple, esto diría correctamente que "Super User"existe en la matriz. Pero también diría que "uper Use"está en la matriz.

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

El problema es que no hay una manera fácil de agregar los anclajes (que se me ocurra) además de recorrer la matriz. A menos que pueda agregarlos antes de ponerlos en la matriz ...

Nifle
fuente
Sin embargo, es una buena solución cuando las constantes son alfanuméricas (con grep "\b$FINDME\b"). Probablemente podría trabajar con constantes no alfanuméricos que no tienen espacios, con "(^| )$FINDME(\$| )"(o algo así ... nunca he podido saber qué sabor de usos de expresiones regulares 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
Cong Nguyen
fuente
1
¿Puede explicar por qué sugiere este enfoque? OP le preguntó si hay una manera de hacerlo sin recorrer la matriz , que es lo que está haciendo in_array. Saludos
bertieb
Bueno, al menos ese ciclo está encapsulado en una función, que podría ser lo suficientemente buena para muchos casos (con pequeños conjuntos de datos) y no requiere bash 4+. Probablemente ${ARRAY[@]}debería usarse.
Tobias