Compruebe si una matriz Bash contiene un valor

443

En Bash, ¿cuál es la forma más simple de probar si una matriz contiene un cierto valor?

Editar : con la ayuda de las respuestas y los comentarios, después de algunas pruebas, se me ocurrió esto:

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

No estoy seguro de si es la mejor solución, pero parece funcionar.

Paolo Tedesco
fuente

Respuestas:

457

Este enfoque tiene la ventaja de no tener que recorrer todos los elementos (al menos no explícitamente). Pero dado que array_to_string_internal()en array.c todavía recorre los elementos de la matriz y los concatena en una cadena, probablemente no sea más eficiente que las soluciones de bucle propuestas, pero es más legible.

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array doesn't contain value
fi

Tenga en cuenta que en los casos en que el valor que está buscando es una de las palabras en un elemento de matriz con espacios, dará falsos positivos. Por ejemplo

array=("Jack Brown")
value="Jack"

La expresión regular verá que "Jack" está en la matriz aunque no lo esté. Por lo tanto, tendrá que cambiar IFSy los caracteres separadores en su expresión regular si aún desea usar esta solución, como esta

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

Esto imprimirá "falso".

Obviamente, esto también se puede usar como una declaración de prueba, lo que permite que se exprese como una frase

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
fuente
1
Agregué un espacio al comienzo de la primera coincidencia del valor regex, para que solo coincida con la palabra, no con algo que termine en la palabra. Funciona genial. Sin embargo, no entiendo por qué usas la segunda condición, ¿no funcionaría bien la primera sola?
JStrahl
1
@AwQiruiGuo No estoy seguro de estar siguiendo. ¿Estás hablando de matrices con literales en dólares? Si es así, solo asegúrate de escapar de los dólares en el valor con el que estás igualando las barras invertidas.
Keegan
10
[[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
Oneliner
3
Shellcheck se queja de esta solución, SC2199 y SC2076. No pude arreglar las advertencias sin romper la funcionalidad. ¿Alguna idea sobre eso aparte de deshabilitar shellcheck para esa línea?
Ali Essam
44
SC2076 es fácil de arreglar, solo elimine las comillas dobles en if. No creo que haya una manera de evitar SC2199 con este enfoque. Tendría que recorrer explícitamente la matriz, como se muestra en algunas de las otras soluciones, o ignorar la advertencia.
Keegan
388

A continuación se muestra una pequeña función para lograr esto. La cadena de búsqueda es el primer argumento y el resto son los elementos de la matriz:

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

Una ejecución de prueba de esa función podría verse así:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
patrik
fuente
55
Funciona muy bien! Sólo tengo que recordar a pasar la matriz con citas como: "${array[@]}". De lo contrario, los elementos que contienen espacios interrumpirán la funcionalidad.
Juve
26
Agradable. Lo llamaría elementIn () porque verifica si el primer argumento está contenido en el segundo. contieneElementos () suena como si la matriz fuera primero. Para los novatos como yo, un ejemplo de cómo usar una función que no escribe en stdout en una declaración "if" ayudaría: ¡ if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Gracias por su ayuda!
GlenPeterson 01 de
55
@Bluz the && construct es un operador booleano AND. El uso de operadores booleanos crea una declaración booleana. La lógica booleana dice que toda la declaración solo puede ser verdadera si ambas declaraciones antes y después de && se evalúan como verdaderas. Esto se usa como un acceso directo en lugar de y si es un bloque. La prueba se evalúa y si es falsa, no hay necesidad de evaluar el retorno ya que es irrelevante para toda la declaración una vez que la prueba ha fallado y, por lo tanto, no se ejecuta. Si la prueba es exitosa, entonces el éxito de la declaración booleana REQUIERE que se determine el resultado del retorno para que se ejecute el código.
peteches
44
@James por convención, el código de éxito en bash es "0" y el error lo es todo> = 1. Es por eso que devuelve 0 en caso de éxito. :)
tftd
11
@Stelios shiftdesplaza la lista de argumentos por 1 a la izquierda (descartando el primer argumento) y forsin initerar implícitamente sobre la lista de argumentos.
Christian el
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
ghostdog74
fuente
69
Tenga en cuenta que esto no se repite en cada elemento de la matriz por separado ... en su lugar, simplemente concatena la matriz y coincide con "dos" como una subcadena. Esto podría causar un comportamiento no deseado si uno está probando si la palabra exacta "dos" es un elemento en la matriz.
MartyMacGyver
Pensé que esto me iba a funcionar al comparar tipos de archivos, pero descubrí que a medida que aumentaban los contadores, contaba demasiados valores ... ¡Boo!
Mike Q
17
¡incorrecto! Motivo: case "${myarray[@]}" in *"t"*) echo "found" ;; esacsalidas:found
Sergej Jevsejev
@MartyMacGyver, ¿podría mirar mi adición a esta respuesta stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin el
45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Para cuerdas:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
Scott
fuente
Dicho esto, puede usar un bucle indexado para y evitar que lo maten cuando un elemento de matriz contiene IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb
@Matt: debe tener cuidado al usar, ${#}ya que Bash admite matrices dispersas.
Pausado hasta nuevo aviso.
@Paolo, si su matriz contiene un espacio, simplemente compárelo como una cadena. un espacio también es una cadena.
Scott
@Paolo: Puede hacer que sea una función, pero las matrices no se pueden pasar como argumentos, por lo que tendrá que tratarlo como global.
Pausado hasta nuevo aviso.
Dennis tiene razón. Del manual de referencia de bash: "Si la palabra está entre comillas dobles, ... $ {name [@]} expande cada elemento de nombre a una palabra separada"
mkb
37

Solución de una línea

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

Explicación

los printf declaración imprime cada elemento de la matriz en una línea separada.

La grepdeclaración utiliza los caracteres especiales ^y $para encontrar una línea que contiene exactamente el patrón dado como mypattern(ni más, ni menos).


Uso

Para poner esto en una if ... thendeclaración:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

Agregué una -qbandera a la grepexpresión para que no imprima coincidencias; solo tratará la existencia de una coincidencia como "verdadera".

JellicleCat
fuente
Buena solución! En GNU grep, también hay "--line-regexp" que podría reemplazar "-P" y ^ y $ en el patrón: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

Si necesita rendimiento, no desea recorrer toda su matriz cada vez que busque.

En este caso, puede crear una matriz asociativa (tabla hash o diccionario) que represente un índice de esa matriz. Es decir, asigna cada elemento de la matriz a su índice en la matriz:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

Entonces puedes usarlo así:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

Y prueba la membresía así:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

O también:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

Tenga en cuenta que esta solución hace lo correcto incluso si hay espacios en el valor probado o en los valores de la matriz.

Como beneficio adicional, también obtiene el índice del valor dentro de la matriz con:

echo "<< ${myarray_index[$member]} >> is the index of $member"
LeoRochael
fuente
+1 para la idea de que debería usar una matriz asociativa. Creo que el código para make_indexes un poco más artificial debido a la indirección; podrías haber usado un nombre de matriz fijo con un código mucho más simple.
musiphil
17

Normalmente solo uso:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

un valor distinto de cero indica que se encontró una coincidencia.

Sean DiSanti
fuente
Es cierto, esta es definitivamente la solución más fácil: en mi opinión, debería ser una respuesta marcada. ¡Al menos ten mi voto! [:
ToVine
2
Eso no funcionará para agujas similares. Por ejemplo,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Muy cierto. unirlo con un delimitador no presente en ningún elemento y agregarlo a la aguja ayudaría con eso. Tal vez algo así como ... (no probado)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
Usar grep -x evitaría falsos positivos: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher
Quizás simplemente inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)porque -x hace que grep intente hacer coincidir toda la cadena de entrada
MI Wright
17

Otro revestimiento sin función:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

¡Gracias @Qwerty por los avisos sobre espacios!

función correspondiente:

find_in_array() {
  local word=$1
  shift
  for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
  return 1
}

ejemplo:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
fuente
1
¿Por qué necesitamos una subshell aquí?
codeforester
1
@codeforester esto es viejo ... pero como fue escrito, lo necesitas para romper, eso es lo que exit 0hace (se detiene lo antes posible si se encuentra).
estani
El final del trazador de líneas debe ser en || echo not foundlugar de || not foundo el shell intentará ejecutar un comando con el nombre de no con argumento encontrado si el valor solicitado no está en la matriz.
zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Ahora maneja matrices vacías correctamente.

Yann
fuente
¿Cómo es esto diferente de la respuesta de @ patrik? La única diferencia que veo es "$e" = "$1"(en lugar de "$e" == "$1") que parece un error.
CivFan
1
No lo es. @ patrik fusionó mi comentario en su respuesta original en ese entonces (parche # 4) Nota: "e" == "$1"es sintácticamente más claro.
Yann
@CivFan En su forma actual, esto es más corto que el de la respuesta de patrik, debido a la elegante $ {@: 2} y la auto-documentación $ 1. Agregaría que no es necesario citar dentro de [[]].
Hontvári Levente
9

Aquí hay una pequeña contribución:

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

Nota: de esta manera no se distingue el caso "dos palabras", pero esto no es obligatorio en la pregunta.

hornetbzz
fuente
Este me ayudó mucho. ¡Gracias!
Ed Manet
La pregunta no decía explícitamente que tenía que dar la respuesta correcta, pero creo que eso está implícito en la pregunta ... La matriz no contiene el valor "dos".
tetsujin
Lo anterior informará una coincidencia para 'rd'.
Noel Yap
6

Si desea hacer una prueba rápida y sucia para ver si vale la pena iterar sobre toda la matriz para obtener una coincidencia precisa, Bash puede tratar las matrices como escalares. Probar una coincidencia en el escalar, si no hay ninguno, saltear el bucle ahorra tiempo. Obviamente puedes obtener falsos positivos.

array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

Esto generará "Comprobación" y "Coincidencia". Con array=(word "two words" something)esto solo saldrá "Comprobando". Con array=(word "two widgets" something)no habrá salida.

Pausado hasta nuevo aviso.
fuente
¿Por qué no simplemente reemplazar wordscon una expresión regular ^words$que coincide solo con la cadena completa, lo que elimina por completo la necesidad de verificar cada elemento individualmente?
Dejay Clayton
@DejayClayton: Porque pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]nunca coincidirá ya que está comprobando toda la matriz a la vez como si fuera un escalar. Las verificaciones individuales en mi respuesta se deben hacer solo si hay una razón para proceder en función de la coincidencia aproximada.
Pausado hasta nuevo aviso.
Ah, veo lo que intentas hacer. He propuesto una respuesta variante que es más eficaz y segura.
Dejay Clayton
6

Esto es trabajo para mí:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

Llamada de ejemplo:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi
Chris Prince
fuente
5
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

Si lo prefiere, puede usar opciones largas equivalentes:

--fixed-strings --quiet --line-regexp --null-data
Steven Penny
fuente
1
Esto no funciona con BSD-grep en Mac, ya que no hay datos nulos. :(
Será
4

Tomando prestado de la respuesta de Dennis Williamson , la siguiente solución combina matrices, citas seguras con shell y expresiones regulares para evitar la necesidad de: iterar sobre bucles; utilizando tuberías u otros subprocesos; o utilizando utilidades que no sean bash.

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

El código anterior funciona utilizando expresiones regulares de Bash para que coincidan con una versión en cadena del contenido de la matriz. Hay seis pasos importantes para garantizar que la coincidencia de expresiones regulares no pueda ser engañada por combinaciones inteligentes de valores dentro de la matriz:

  1. Construya la cadena de comparación utilizando el Bash incorporado printf concha citar, %q. Las comillas de shell garantizarán que los caracteres especiales se vuelvan "seguros para shell" al escapar con una barra diagonal inversa \.
  2. Elija un carácter especial para que sirva como delimitador de valor. El delimitador TIENE que ser uno de los caracteres especiales que se escaparán al usar%q ; esa es la única manera de garantizar que los valores dentro de la matriz no se puedan construir de manera inteligente para engañar a la coincidencia de expresiones regulares. Elijo una coma ,porque ese personaje es el más seguro cuando se evalúa o se usa incorrectamente de una manera inesperada.
  3. Combine todos los elementos de la matriz en una sola cadena, utilizando dos instancias del carácter especial para servir como delimitador. Usando una coma como ejemplo, usé,,%q como argumento para printf. Esto es importante porque dos instancias del carácter especial solo pueden aparecer una al lado de la otra cuando aparecen como delimitador; todas las demás instancias del personaje especial se escaparán.
  4. Agregue dos instancias finales del delimitador a la cadena, para permitir coincidencias con el último elemento de la matriz. Por lo tanto, en lugar de comparar contra ${array_str}, comparar contra ${array_str},,.
  5. Si la cadena de destino que está buscando es suministrada por una variable de usuario, usted debe escapar de todas las instancias del carácter especial con una barra diagonal inversa. De lo contrario, la coincidencia de expresión regular se vuelve vulnerable a ser engañada por elementos de matriz ingeniosamente diseñados.
  6. Realice una coincidencia de expresión regular Bash contra la cadena.
Dejay Clayton
fuente
Muy inteligente. Puedo ver que se previenen la mayoría de los problemas potenciales, pero me gustaría probar para ver si hay casos de esquina. Además, me gustaría ver un ejemplo de manejo del punto 5. Algo así, printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]tal vez.
Pausado hasta nuevo aviso.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino
3

Una pequeña adición a la respuesta de @ ghostdog74 sobre el uso de la caselógica para verificar que la matriz contiene un valor particular:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

O con la extglobopción activada, puede hacerlo así:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

También podemos hacerlo con la ifdeclaración:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
Aleksandr Podkutin
fuente
2

dado:

array=("something to search for" "a string" "test2000")
elem="a string"

luego una simple verificación de:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

dónde

c is element separator
p is regex pattern

(La razón para asignar p por separado, en lugar de usar la expresión directamente dentro de [[]] es para mantener la compatibilidad para bash 4)

Beorn Harris
fuente
me encanta tu uso de la palabra "simple" aquí ... 😂
Christian
2

Combinando algunas de las ideas presentadas aquí, puede hacer una declaración elegante sin bucles que coincida exactamente con las palabras .

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

Esto no se activará wordo valsolo coincidirán las palabras completas. Se romperá si cada valor de matriz contiene varias palabras.

Ecker00
fuente
1

En general, escribo este tipo de utilidades para operar sobre el nombre de la variable, en lugar del valor de la variable, principalmente porque bash no puede pasar variables por referencia.

Aquí hay una versión que funciona con el nombre de la matriz:

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

Con esto, el ejemplo de la pregunta se convierte en:

array_contains A "one" && echo "contains one"

etc.

Barry Kelly
fuente
¿Alguien puede publicar un ejemplo de esto usado dentro de un if, particularmente cómo se pasa en la matriz? Estoy tratando de verificar si se pasó un argumento al script al tratar los parámetros como una matriz, pero no quiere funcionar. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; entonces .... Pero cuando ejecuta el script con 'asas' como argumento, sigue diciendo asas: comando no encontrado. : /
Steve Childs
1

Utilizando grep yprintf

Formatee cada miembro de la matriz en una nueva línea, luego greplas líneas.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
ejemplo:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

Tenga en cuenta que esto no tiene problemas con delimitadores y espacios.

QWERTY
fuente
1

Verificación de una línea sin 'grep' y bucles

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

Este enfoque no utiliza utilidades externas como grep ni bucles.

Lo que pasa aquí es:

  • utilizamos un emparejador de subcadenas comodín para encontrar nuestro elemento en la matriz que se concatena en una cadena;
  • cortamos posibles falsos positivos al encerrar nuestro elemento de búsqueda entre un par de delimitadores;
  • usamos un carácter no imprimible como delimitador, para estar seguros;
  • también logramos que nuestro delimitador se use para la concatenación de matrices mediante el reemplazo temporal IFS valor variable;
  • hacemos que este IFSreemplazo de valor sea temporal mediante la evaluación de nuestra expresión condicional en un sub-shell (dentro de un par de paréntesis)
Sergey Ushakov
fuente
Eliminar dlm. Use IFS directamente.
Robin A. Meade
Esta es la mejor respuesta. Me gustó tanto que escribí una función usando esta técnica .
Robin A. Meade
1

Usando la expansión de parámetros:

$ {parámetro: + palabra} Si el parámetro es nulo o no está establecido, no se sustituye nada; de lo contrario, se sustituye la expansión de palabra.

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done
Gabriel Laden
fuente
${myarray[hello]:+_}funciona muy bien para matrices asociativas, pero no para matrices indexadas habituales. La pregunta es sobre encontrar un valor en un aray, no verificar si existe la clave de una matriz asociativa.
Eric
0

Después de haber respondido, leí otra respuesta que me gustó especialmente, pero fue defectuosa y rechazada. Me inspiré y aquí hay dos nuevos enfoques que veo viables.

array=("word" "two words") # let's look for "two words"

usando grepy printf:

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

utilizando for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

Para resultados no encontrados, agregue || <run_your_if_notfound_command_here>

QWERTY
fuente
0

Aquí está mi opinión sobre esto.

Prefiero no usar un bash for loop si puedo evitarlo, ya que lleva tiempo ejecutarlo. Si algo tiene que repetirse, deje que sea algo que se haya escrito en un lenguaje de nivel inferior que un script de shell.

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

Esto funciona creando una matriz asociativa temporal _arr, cuyos índices se derivan de los valores de la matriz de entrada. (Tenga en cuenta que las matrices asociativas están disponibles en bash 4 y superiores, por lo que esta función no funcionará en versiones anteriores de bash).$IFS para evitar la división de palabras en espacios en blanco.

La función no contiene bucles explícitos, aunque internamente bash recorre la matriz de entrada para rellenar printf. El formato printf se utiliza %qpara garantizar que los datos de entrada se escapen de modo que puedan usarse de forma segura como claves de matriz.

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

Tenga en cuenta que todo lo que utiliza esta función está integrado en bash, por lo que no hay tuberías externas que lo arrastren, incluso en la expansión del comando.

Y si no te gusta usar eval... bueno, eres libre de usar otro enfoque. :-)

ghoti
fuente
¿Qué pasa si la matriz contiene corchetes?
gniourf_gniourf
@gniourf_gniourf: parece estar bien si los corchetes están equilibrados, pero puedo ver que es un problema si su matriz incluye valores con corchetes desequilibrados. En ese caso, invocaría las evalinstrucciones al final de la respuesta. :)
ghoti
No es que no me guste eval(no tengo nada en contra, a diferencia de la mayoría de las personas que lloran evales malvada, sobre todo sin entender lo que es malo). Solo que tu comando está roto. Quizás en %qlugar de %ssería mejor.
gniourf_gniourf
1
@gniourf_gniourf: Solo quise decir el bit de "otro enfoque" (y estoy totalmente de acuerdo contigo eval, obviamente), pero tienes toda la razón, %qparece ayudar, sin romper nada más que pueda ver. (No me di cuenta de que% q también escaparía a los corchetes). Otro problema que vi y solucioné fue con respecto al espacio en blanco. Con a=(one "two " three), similar al problema de Keegan: no solo array_contains a "two "obtuvo un falso negativo, sino que array_contains a twoobtuvo un falso positivo. Suficientemente fácil de arreglar mediante la configuración IFS.
ghoti
Con respecto a los espacios en blanco, ¿no es porque faltan comillas? también se rompe con personajes glob. Creo que quieres esto en su lugar: eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )y puedes deshacerte de él local IFS=. Todavía hay un problema con los campos vacíos en la matriz, ya que Bash se negará a crear una clave vacía en una matriz asociativa. Una forma rápida y rápida de solucionarlo es anteponer un personaje ficticio, por ejemplo x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )y return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf
-1

Mi versión de la técnica de expresiones regulares que ya se ha sugerido:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

Lo que sucede aquí es que está expandiendo toda la matriz de valores admitidos en palabras y anteponiendo una cadena específica, "X-" en este caso, a cada uno de ellos, y haciendo lo mismo con el valor solicitado. Si este está realmente contenido en la matriz, entonces la cadena resultante coincidirá como máximo con uno de los tokens resultantes, o ninguno en el contrario. En el último caso, el || El operador se dispara y usted sabe que está tratando con un valor no compatible. Antes de todo eso, el valor solicitado se elimina de todos los espacios en blanco iniciales y finales a través de la manipulación de cadena de shell estándar.

Creo que es limpio y elegante, aunque no estoy muy seguro de qué tan eficaz puede ser si su conjunto de valores admitidos es particularmente grande.

jmpp
fuente
-1

Aquí está mi opinión sobre este problema. Aquí está la versión corta:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

Y la versión larga, que creo que es mucho más fácil a la vista.

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

Ejemplos:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
robert
fuente
No he estado usando bash durante tanto tiempo ahora que tengo dificultades para comprender las respuestas, o incluso lo que escribí yo mismo :) No puedo creer que esta pregunta siga recibiendo actividad después de todo este tiempo :)
Paolo Tedesco
¿Qué hay de test_arr=("hello" "world" "two words")?
Qwerty
-1

Tuve el caso de que tenía que verificar si una ID estaba contenida en una lista de ID generadas por otro script / comando. Para mí funcionó lo siguiente:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

También podría acortarlo / compactarlo así:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

En mi caso, estaba ejecutando jq para filtrar algunos JSON para una lista de ID y luego tuve que verificar si mi ID estaba en esta lista y funcionó mejor para mí. No funcionará para matrices del tipo creadas manualmente, LIST=("1" "2" "4")sino para salidas de script separadas por nueva línea.


PD .: no pude comentar una respuesta porque soy relativamente nuevo ...

E. Körner
fuente
-2

El siguiente código verifica si un valor dado está en la matriz y devuelve su desplazamiento basado en cero:

A=("one" "two" "three four")
VALUE="two"

if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
  echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
  echo "Couldn't find $VALUE"
fi

La coincidencia se realiza en los valores completos, por lo tanto, establecer VALOR = "tres" no coincidiría.

Sven Rieke
fuente
-2

Vale la pena investigar esto si no quieres iterar:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

Fragmento adaptado de: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Creo que es bastante inteligente.

EDITAR: Probablemente podrías hacer:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

Pero este último solo funciona si la matriz contiene valores únicos. Buscar 1 en "143" dará falsos positivos, me parece.

Sigg3.net
fuente
-2

Un poco tarde, pero podrías usar esto:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi
Codificador-256
fuente
-2

Se me ocurrió esta, que funciona solo en zsh, pero creo que el enfoque general es bueno.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

Sacas tu patrón de cada elemento solo si comienza ${arr[@]/#pattern/}o termina ${arr[@]/%pattern/}con él. Estas dos sustituciones funcionan en bash, pero ambas al mismo tiempo.${arr[@]/#%pattern/} solo funcionan en zsh.

Si la matriz modificada es igual a la original, entonces no contiene el elemento.

Editar:

Este funciona en bash:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

Después de la sustitución, compara la longitud de ambas matrices. Obviamente, si la matriz contiene el elemento, la sustitución lo eliminará por completo y el recuento será diferente.

spelufo
fuente