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.
[[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
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.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:
Una ejecución de prueba de esa función podría verse así:
fuente
"${array[@]}"
. De lo contrario, los elementos que contienen espacios interrumpirán la funcionalidad.if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi;
Gracias por su ayuda!shift
desplaza la lista de argumentos por 1 a la izquierda (descartando el primer argumento) yfor
sinin
iterar implícitamente sobre la lista de argumentos.fuente
case "${myarray[@]}" in *"t"*) echo "found" ;; esac
salidas:found
Para cuerdas:
fuente
${#}
ya que Bash admite matrices dispersas.Solución de una línea
Explicación
los
printf
declaración imprime cada elemento de la matriz en una línea separada.La
grep
declaración utiliza los caracteres especiales^
y$
para encontrar una línea que contiene exactamente el patrón dado comomypattern
(ni más, ni menos).Uso
Para poner esto en una
if ... then
declaración:Agregué una
-q
bandera a lagrep
expresión para que no imprima coincidencias; solo tratará la existencia de una coincidencia como "verdadera".fuente
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:
Entonces puedes usarlo así:
Y prueba la membresía así:
O también:
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:
fuente
make_index
es 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.Normalmente solo uso:
un valor distinto de cero indica que se encontró una coincidencia.
fuente
haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)
porque -x hace que grep intente hacer coincidir toda la cadena de entradaOtro revestimiento sin función:
¡Gracias @Qwerty por los avisos sobre espacios!
función correspondiente:
ejemplo:
fuente
exit 0
hace (se detiene lo antes posible si se encuentra).|| echo not found
lugar de|| not found
o el shell intentará ejecutar un comando con el nombre de no con argumento encontrado si el valor solicitado no está en la matriz.Ahora maneja matrices vacías correctamente.
fuente
"$e" = "$1"
(en lugar de"$e" == "$1"
) que parece un error."e" == "$1"
es sintácticamente más claro.Aquí hay una pequeña contribución:
Nota: de esta manera no se distingue el caso "dos palabras", pero esto no es obligatorio en la pregunta.
fuente
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.
Esto generará "Comprobación" y "Coincidencia". Con
array=(word "two words" something)
esto solo saldrá "Comprobando". Conarray=(word "two widgets" something)
no habrá salida.fuente
words
con una expresión regular^words$
que coincide solo con la cadena completa, lo que elimina por completo la necesidad de verificar cada elemento individualmente?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.Esto es trabajo para mí:
Llamada de ejemplo:
fuente
Si lo prefiere, puede usar opciones largas equivalentes:
fuente
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.
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:
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\
.%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.,,%q
como argumento paraprintf
. 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.${array_str}
, comparar contra${array_str},,
.fuente
printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]
tal vez.case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Una pequeña adición a la respuesta de @ ghostdog74 sobre el uso de la
case
lógica para verificar que la matriz contiene un valor particular:O con la
extglob
opción activada, puede hacerlo así:También podemos hacerlo con la
if
declaración:fuente
dado:
luego una simple verificación de:
dónde
(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)
fuente
Combinando algunas de las ideas presentadas aquí, puede hacer una declaración elegante sin bucles que coincida exactamente con las palabras .
Esto no se activará
word
oval
solo coincidirán las palabras completas. Se romperá si cada valor de matriz contiene varias palabras.fuente
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:
Con esto, el ejemplo de la pregunta se convierte en:
etc.
fuente
Utilizando
grep
yprintf
Formatee cada miembro de la matriz en una nueva línea, luego
ejemplo:grep
las líneas.Tenga en cuenta que esto no tiene problemas con delimitadores y espacios.
fuente
Verificación de una línea sin 'grep' y bucles
Este enfoque no utiliza utilidades externas como
grep
ni bucles.Lo que pasa aquí es:
IFS
valor variable;IFS
reemplazo de valor sea temporal mediante la evaluación de nuestra expresión condicional en un sub-shell (dentro de un par de paréntesis)fuente
Usando la expansión de parámetros:
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.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.
usando
grep
yprintf
:utilizando
for
:Para resultados no encontrados, agregue
|| <run_your_if_notfound_command_here>
fuente
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.
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%q
para garantizar que los datos de entrada se escapen de modo que puedan usarse de forma segura como claves de matriz.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. :-)fuente
eval
instrucciones al final de la respuesta. :)eval
(no tengo nada en contra, a diferencia de la mayoría de las personas que lloraneval
es malvada, sobre todo sin entender lo que es malo). Solo que tu comando está roto. Quizás en%q
lugar de%s
sería mejor.eval
, obviamente), pero tienes toda la razón,%q
parece 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. Cona=(one "two " three)
, similar al problema de Keegan: no soloarray_contains a "two "
obtuvo un falso negativo, sino quearray_contains a two
obtuvo un falso positivo. Suficientemente fácil de arreglar mediante la configuraciónIFS
.eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )
y puedes deshacerte de éllocal 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 ejemplox
:eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )
yreturn $(( 1 - 0${_arr[x$2]} ))
.Mi versión de la técnica de expresiones regulares que ya se ha sugerido:
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.
fuente
Aquí está mi opinión sobre este problema. Aquí está la versión corta:
Y la versión larga, que creo que es mucho más fácil a la vista.
Ejemplos:
fuente
test_arr=("hello" "world" "two words")
?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:
También podría acortarlo / compactarlo así:
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 ...
fuente
El siguiente código verifica si un valor dado está en la matriz y devuelve su desplazamiento basado en cero:
La coincidencia se realiza en los valores completos, por lo tanto, establecer VALOR = "tres" no coincidiría.
fuente
Vale la pena investigar esto si no quieres iterar:
Fragmento adaptado de: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Creo que es bastante inteligente.
EDITAR: Probablemente podrías hacer:
Pero este último solo funciona si la matriz contiene valores únicos. Buscar 1 en "143" dará falsos positivos, me parece.
fuente
Un poco tarde, pero podrías usar esto:
fuente
Se me ocurrió esta, que funciona solo en zsh, pero creo que el enfoque general es bueno.
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:
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.
fuente