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