strings script read print example array arrays bash

arrays - script - linux bash array example



Compruebe si una matriz Bash contiene un valor (30)

Usando grep y printf

Formatee cada miembro de la matriz en una nueva línea, luego grep las 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.

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

Edit : 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.


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 como:

$ array=("something to search for" "a string" "test2000") $ containsElement "a string" "${array[@]}" $ echo $? 0 $ containsElement "blaha" "${array[@]}" $ echo $? 1


Ampliando la respuesta anterior de Sean DiSanti, creo que la siguiente es una solución simple y elegante que evita tener que pasar por la matriz y no dar falsos positivos debido a coincidencias parciales

function is_in_array { local ELEMENT="${1}" local DELIM="," printf "${DELIM}%s${DELIM}" "${@:2}" | grep -q "${DELIM}${ELEMENT}${DELIM}" }

Que se puede llamar así:

$ haystack=("needle1" "needle2" "aneedle" "spaced needle") $ is_in_array "needle" "${haystack[@]}" $ echo $? 1 $ is_in_array "needle1" "${haystack[@]}" $ echo $? 0


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


Aquí está mi opinión sobre esto.

Prefiero no usar un bash for loop si puedo evitarlo, ya que toma tiempo ejecutarlo. Si algo tiene que hacer un bucle, deje que se escriba 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 superior, por lo que esta función no funcionará en versiones anteriores de bash). Establecimos $IFS para evitar la división de palabras en espacios en blanco.

La función no contiene bucles explícitos, aunque internamente se realizan pasos a través de la matriz de entrada para rellenar printf . El formato printf usa %q para asegurar que los datos de entrada se escapen de manera que puedan usarse de manera 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 esta función usa está integrado para bash, por lo que no hay tuberías externas que lo estén arrastrando hacia abajo, incluso en la expansión de comandos.

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


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 distingue el caso "dos palabras", pero esto no se requiere en la pregunta.


Aunque hubo varias respuestas excelentes y útiles aquí, no encontré una que pareciera ser la combinación correcta de performante, multiplataforma y robusta; Así que quería compartir la solución que escribí para mi código:

#!/bin/bash # array_contains "$needle" "${haystack[@]}" # # Returns 0 if an item ($1) is contained in an array ($@). # # Developer note: # The use of a delimiter here leaves something to be desired. The ideal # method seems to be to use `grep` with --line-regexp and --null-data, but # Mac/BSD grep doesn''t support --line-regexp. function array_contains() { # Extract and remove the needle from $@. local needle="$1" shift # Separates strings in the array for matching. Must be extremely-unlikely # to appear in the input array or the needle. local delimiter=''#!-/8/-!#'' # Create a string with containing every (delimited) element in the array, # and search it for the needle with grep in fixed-string mode. if printf "${delimiter}%s${delimiter}" "$@" | / grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then return 0 fi return 1 }


Combinando algunas de las ideas presentadas aquí, puede hacer una declaración elegante sin bucles que haga coincidencias exactas de 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á en word o val , solo coincidencias de palabras completas. Se romperá si cada valor de la matriz contiene varias palabras.


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

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

utilizando grep y 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 los resultados de not_found agregue || <run_your_if_notfound_command_here> || <run_your_if_notfound_command_here>


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á.


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 aún 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 arr contains value fi if [[ ! " ${array[@]} " =~ " ${value} " ]]; then # whatever you want to do when arr 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"

El regex verá que Jack está en la matriz aunque no lo esté. Así que tendrá que cambiar IFS y los caracteres separadores en su expresión regular si desea seguir utilizando esta solución, como esta

IFS=$''/t'' array=("Jack Brown/tJack Smith") unset IFS value="Jack Smith" if [[ "/t${array[@]}/t" =~ "/t${value}/t" ]]; then echo "yep, it''s there" fi


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://.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 }

Ejemplo de llamada:

arr=("abc" "xyz" "123") if contains arr "abcx"; then echo "Yes" else echo "No" fi


Generalmente escribo este tipo de utilidades para operar con el nombre de la variable, en lugar del valor de la variable, principalmente porque bash no puede pasar las 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.


Hay un código de ejemplo que muestra cómo reemplazar una subcadena de una matriz . Puede hacer una copia de la matriz e intentar eliminar el valor objetivo de la copia. Si la copia y el original son diferentes, entonces el valor objetivo existe en la cadena original.

La solución sencilla (pero potencialmente más lenta) consiste en iterar a través de toda la matriz y comprobar cada elemento individualmente. Esto es lo que normalmente hago porque es fácil de implementar y puede envolverlo en una función (vea esta información sobre cómo pasar una matriz a una función ).


Mi versión de la técnica de expresiones regulares que ya ha sido sugerida:

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 la matriz completa 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 éste está realmente contenido en la matriz, entonces la cadena resultante, como máximo, coincidirá con uno de los tokens resultantes, o ninguno en absoluto. En este último caso el || el operador se dispara y usted sabe que está tratando con un valor no soportado. 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 cadenas de shell estándar.

Creo que es limpio y elegante, aunque no estoy muy seguro de su rendimiento si la variedad de valores admitidos es particularmente grande.


Normalmente solo uso:

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

el valor no cero indica que se encontró una coincidencia.


Otro forro sin función:

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

Gracias a @Qwerty por los heads-up con respecto a los espacios!

función correspondiente:

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

ejemplo:

some_words=( these are some words ) find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"


Podría valer la pena investigar 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.

EDIT: Probablemente podría 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, creo.


Si desea realizar una prueba rápida y sucia para ver si vale la pena recorrer toda la matriz para obtener una coincidencia precisa, Bash puede tratar las matrices como si fueran escalares. Pruebe si hay una coincidencia en el escalar, si no hay ninguno, entonces saltarse el bucle ahorra tiempo. Obviamente usted puede 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 dará como resultado "Comprobación" y "Coincidencia". Con array=(word "two words" something) solo se mostrará "Comprobando". Con array=(word "two widgets" something) no habrá salida.


Si necesita rendimiento, no desea recorrer toda su matriz cada vez que realiza una búsqueda.

En este caso, puede crear una matriz asociativa (tabla hash o diccionario) que represente un índice de esa matriz. Es decir, mapea cada elemento de la matriz en 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 de membresía como tal:

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

Observe 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"


Tenía el caso de que tenía que comprobar si una ID estaba contenida en una lista de ID generadas por otro script / comando. Para mí trabajé 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ías 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 obtener una lista de ID y luego tuve que comprobar si mi ID estaba en esta lista y esto funcionó mejor para mí. No funcionará para matrices creadas manualmente del tipo LIST=("1" "2" "4") pero con salida de secuencia de comandos separada de nueva línea.

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


Tomando prestada la answer de Dennis Williamson , la siguiente solución combina matrices, citas seguras con shell y expresiones regulares para evitar la necesidad de: iterar sobre loops; utilizando tuberías u otros subprocesos; o usando utilidades que no sean de 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 al usar expresiones regulares Bash para coincidir con una versión de cadena del contenido de la matriz. Hay seis pasos importantes para garantizar que la coincidencia de la expresión regular no pueda ser engañada por combinaciones inteligentes de valores dentro de la matriz:

  1. Construya la cadena de comparación utilizando las comillas de impresión incorporadas de Bash, %q . La cotización del shell asegurará que los caracteres especiales se vuelvan "seguros para el shell" al escaparse con la barra invertida / .
  2. Elija un carácter especial para servir como un 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 puedan construirse de manera inteligente para engañar a la expresión regular. Elijo una coma , porque ese personaje es el más seguro cuando se evalúa o se usa mal de una manera inesperada.
  3. Combine todos los elementos de la matriz en una sola cadena, utilizando dos instancias del carácter especial para que sirva como delimitador. Usando coma como ejemplo, usé ,,%q como el 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 delimitadores; Todas las demás instancias del personaje especial serán escapadas.
  4. Agregue dos instancias finales del delimitador a la cadena, para permitir coincidencias contra el último elemento de la matriz. Por lo tanto, en lugar de comparar con ${array_str} , compare con ${array_str},, .
  5. Si la cadena de destino que busca está provista por una variable de usuario, debe escapar de todas las instancias del carácter especial con una barra invertida. De lo contrario, la coincidencia de expresiones regulares se vuelve vulnerable a ser engañada por elementos de arreglos hábilmente diseñados.
  6. Realizar una coincidencia de expresión regular Bash contra la cadena.

Una combinación de respuestas de Beorn Harris y loentar da una prueba más interesante de una sola línea:

delim=$''/x1F'' # define a control code to be used as more or less reliable delimiter if [[ "${delim}${array[@]}${delim}" =~ "${delim}a string to test${delim}" ]]; then echo "contains ''a string to test''" fi

Este no usa funciones adicionales, no hace reemplazos para las pruebas y agrega protección adicional contra coincidencias falsas ocasionales usando un código de control como delimitador.


Una pequeña adición a la respuesta de @ ghostdog74 sobre el uso de la lógica de case 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 opción extglob activada, puedes hacerlo así:

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

También podemos hacerlo con sentencia if :

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


dado:

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

A continuación, una simple comprobació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 mantener la compatibilidad para bash 4)


Solución de una línea

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

Explicación

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

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

Uso

Para poner esto en un if ... then declaración:

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

-q un indicador -q a la expresión grep para que no imprima coincidencias; simplemente tratará la existencia de un partido como "verdadero".


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


a=(b c d) if printf ''%s/0'' "${a[@]}" | grep -Fqxz c then echo ''array “a” contains value “c”'' fi

Si prefieres puedes usar opciones largas equivalentes:

--fixed-strings --quiet --line-regexp --null-data


containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Ahora maneja las matrices vacías correctamente.


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