Vérifier si un tableau Bash contient une valeur

Dans Bash, quel est le moyen le plus simple de tester si un tableau contient une certaine valeur?

Edit : Avec l’aide des réponses et des commentaires, après quelques tests, j’ai trouvé ceci:

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 

Je ne sais pas si c’est la meilleure solution, mais cela semble fonctionner.

Il existe un exemple de code qui montre comment remplacer une sous-chaîne à partir d’un tableau . Vous pouvez faire une copie du tableau et essayer de supprimer la valeur cible de la copie. Si la copie et l’original sont alors différents, la valeur cible existe dans la chaîne d’origine.

La solution simple (mais qui prend plus de temps) consiste à simplement parcourir l’intégralité du tableau et à vérifier chaque élément individuellement. C’est ce que je fais généralement car il est facile à implémenter et vous pouvez l’envelopper dans une fonction (voir cette information sur le passage d’un tableau à une fonction ).

Voici une petite fonction pour y parvenir. La chaîne de recherche est le premier argument et le rest les éléments du tableau:

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

Un test de cette fonction pourrait ressembler à ceci:

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

Cette approche a l’avantage de ne pas avoir besoin de passer en revue tous les éléments (du moins pas explicitement). Mais comme array_to_ssortingng_internal() dans array.c continue à parcourir les éléments du tableau et à les concaténer en une chaîne, ce n’est probablement pas plus efficace que les solutions en boucle proposées, mais c’est plus lisible.

 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 

Notez que dans les cas où la valeur que vous recherchez est l’un des mots d’un élément de tableau avec des espaces, cela donnera des faux positifs. Par exemple

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

La regex verra Jack comme étant dans le tableau même si ce n’est pas le cas. Donc, vous devrez changer IFS et les séparateurs sur votre regex si vous souhaitez toujours utiliser cette solution, comme ceci

 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 
 $ myarray=(one two three) $ case "${myarray[@]}" in *"two"*) echo "found" ;; esac found 
 for i in "${array[@]}" do if [ "$i" -eq "$yourValue" ] ; then echo "Found" fi done 

Pour les chaînes:

 for i in "${array[@]}" do if [ "$i" == "$yourValue" ] ; then echo "Found" fi done 

J’utilise généralement juste:

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

une valeur non nulle indique qu’une correspondance a été trouvée.

Si vous avez besoin de performances, vous ne souhaitez pas effectuer une boucle sur l’ensemble de votre tableau à chaque recherche.

Dans ce cas, vous pouvez créer un tableau associatif (table de hachage ou dictionnaire) qui représente un index de ce tableau. Par exemple, il mappe chaque élément du tableau dans son index dans le tableau:

 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 } 

Ensuite, vous pouvez l’utiliser comme ceci:

 myarray=('aa' 'bb' 'c c') make_index myarray_index "${myarray[@]}" 

Et testez les membres comme ça:

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

Ou aussi:

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

Notez que cette solution fait ce qu’il faut, même s’il y a des espaces dans la valeur testée ou dans les valeurs du tableau.

En prime, vous obtenez également l’index de la valeur dans le tableau avec:

 echo "<< ${myarray_index[$member]} >> is the index of $member" 

Voici une petite consortingbution:

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

Remarque: cette façon de faire ne distingue pas la casse “deux mots” mais cela n’est pas obligatoire dans la question.

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

Maintenant, gère correctement les tableaux vides.

Un autre paquebot sans fonction:

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

Merci @Qwerty pour la direction des espaces!

fonction correspondante:

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

Exemple:

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

Si vous voulez faire un test rapide et sale pour voir si cela vaut la peine d’effectuer une itération sur l’ensemble du tableau pour obtenir une correspondance précise, Bash peut traiter les tableaux comme des scalaires. Tester une correspondance dans le scalaire, si aucun ne saute la boucle, cela permet de gagner du temps. De toute évidence, vous pouvez obtenir des faux positifs.

 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 

Cela va afficher “Checking” et “Match”. Avec array=(word "two words" something) il ne sortira que “Checking”. Avec array=(word "two widgets" something) il n’y aura pas de sortie.

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

Si vous préférez, vous pouvez utiliser des options longues équivalentes:

 --fixed-ssortingngs --quiet --line-regexp --null-data 

Cela fonctionne pour moi:

 # 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 } 

Exemple d’appel:

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

donné :

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

puis une simple vérification de:

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

 c is element separator p is regex pattern 

(La raison pour atsortingbuer p séparément, plutôt que d’utiliser l’expression directement à l’intérieur de [[]], est de maintenir la compatibilité pour bash 4)

J’écris généralement ce genre d’utilitaires pour opérer sur le nom de la variable, plutôt que sur la valeur de la variable, principalement parce que bash ne peut autrement transmettre les variables par référence.

Voici une version qui fonctionne avec le nom du tableau:

 function array_contains # array value { [[ -n "$1" && -n "$2" ]] || { echo "usage: array_contains  " 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 } 

Avec ceci, l’exemple de question devient:

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

etc.

Utiliser grep et printf

Formatez chaque membre du tableau sur une nouvelle ligne, puis grep les lignes.

 if printf '%s\n' "${array[@]}" | grep -x -q "search ssortingng"; then echo true; else echo false; fi 

Exemple:

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

Notez que cela n’a pas de problèmes avec les délimètres et les espaces.

Simple si cela ne vous dérange pas des expressions régulières:

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

L’instruction printf imprime chaque élément du tableau sur une ligne distincte.

L’instruction grep utilise les caractères spéciaux ^ et $ pour trouver une ligne contenant exactement le motif donné par mypattern .

Après avoir répondu, j’ai lu une autre réponse que j’ai particulièrement appréciée, mais qui a été entachée d’irrégularités. Je me suis inspiré et voici deux nouvelles approches que je vois viables.

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

en utilisant grep et printf :

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

en utilisant for :

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

Pour les résultats not_found add || ||

Voici mon sharepoint vue à ce sujet.

Je préfère ne pas utiliser un bash pour une boucle si je peux l’éviter, car cela prend du temps à courir. Si quelque chose doit boucler, laissez-le être quelque chose qui a été écrit dans un langage de niveau inférieur à celui d’un script shell.

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

Cela fonctionne en créant un tableau associatif temporaire, _arr , dont les index sont dérivés des valeurs du tableau en entrée. (Notez que les tableaux associatifs sont disponibles dans bash 4 et au-dessus, donc cette fonction ne fonctionnera pas dans les versions antérieures de bash.) Nous définissons $IFS pour éviter le fractionnement des mots sur les espaces.

La fonction ne contient pas de boucles explicites, mais en interne bash les étapes à travers le tableau d’entrée afin de remplir printf . Le format printf utilise %q pour garantir que les données en entrée sont échappées de manière à pouvoir être utilisées en toute sécurité comme clés de tableau.

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

Notez que tout ce que cette fonction utilise est un intégré à bash, donc il n’y a pas de canaux externes vous faisant glisser, même dans l’expansion de la commande.

Et si vous n’aimez pas utiliser eval … eh bien, vous êtes libre d’utiliser une autre approche. 🙂

En combinant quelques-unes des idées présentées ici, vous pouvez créer un état élégant sans boucles qui correspond exactement aux mots .

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

Cela ne déclenchera pas le word ou le val , seulement les correspondances de mots entiers. Il se cassera si chaque valeur de tableau contient plusieurs mots.

Empruntant la réponse de Dennis Williamson , la solution suivante combine des tableaux, des guillemets sûrs et des expressions régulières pour éviter de: itérer sur des boucles; utiliser des tuyaux ou d’autres sous-processus; ou en utilisant des utilitaires non-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 

Le code ci-dessus fonctionne en utilisant des expressions régulières Bash pour correspondre à une version rigoureuse du contenu du tableau. Il y a six étapes importantes pour s’assurer que la correspondance des expressions régulières ne peut pas être trompée par des combinaisons intelligentes de valeurs dans le tableau:

  1. Construisez la chaîne de comparaison en utilisant les guillemets printf intégrés de Bash, %q . Shell-quoting garantira que les caractères spéciaux deviennent “shell-safe” en étant échappés avec une barre oblique inverse \ .
  2. Choisissez un caractère spécial pour servir de délimiteur de valeur. Le délimiteur DOIT être l’un des caractères spéciaux qui seront échappés lors de l’utilisation de %q ; c’est la seule façon de garantir que les valeurs du tableau ne peuvent pas être construites de manière intelligente pour tromper la correspondance des expressions régulières. Je choisis la virgule , car ce caractère est le plus sûr lorsqu’il est évalué ou mal utilisé d’une manière inattendue.
  3. Combinez tous les éléments du tableau en une seule chaîne, en utilisant deux instances du caractère spécial pour servir de délimiteur. En utilisant la virgule comme exemple, j’ai utilisé ,,%q comme argument de printf . Ceci est important car deux instances du caractère spécial ne peuvent apparaître que côte à côte lorsqu’elles apparaissent comme le délimiteur; toutes les autres instances du caractère spécial seront échappées.
  4. Ajoutez deux instances finales du délimiteur à la chaîne pour autoriser les correspondances avec le dernier élément du tableau. Ainsi, au lieu de comparer avec ${array_str} , comparez avec ${array_str},, .
  5. Si la chaîne cible que vous recherchez est fournie par une variable utilisateur, vous devez échapper toutes les occurrences du caractère spécial avec une barre oblique inverse. Sinon, la correspondance des expressions régulières devient vulnérable à être trompée par des éléments de tableau intelligemment conçus.
  6. Effectuez une correspondance d’expressions régulières Bash avec la chaîne.

Voici mon sharepoint vue sur ce problème. Voici la version courte:

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

Et la version longue, qui me semble beaucoup plus facile pour les yeux.

 # 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$" } 

Exemples:

 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 

J’ai eu le cas que je devais vérifier si un identifiant était contenu dans une liste d’ID générés par un autre script / commande. Pour moi a travaillé comme suit:

 # the ID I was looking for ID=1 # somehow generated list of IDs LIST=$(  ) # 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 (eg 0 for nothing found, everything greater than 0 means found) if [ ISIN -eq 0 ]; then echo "not found" fi # etc. 

Vous pouvez également le raccourcir / compacter comme ceci:

 if [ $(echo " $(  

Dans mon cas, j'utilisais jq pour filtrer du JSON pour une liste d'ID et je devais vérifier plus tard si mon identifiant figurait dans cette liste et que cela fonctionnait le mieux pour moi. Cela ne fonctionnera pas pour les tableaux créés manuellement de type LIST=("1" "2" "4") mais avec une sortie de script séparé par nouvelle ligne.


PS: ne pouvait pas commenter une réponse parce que je suis relativement nouveau ...

Le code suivant vérifie si une valeur donnée est dans le tableau et renvoie son décalage basé sur zéro:

 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 correspondance est faite sur les valeurs complètes, par conséquent, définir VALUE = “trois” ne correspond pas.

Cela pourrait être intéressant si vous ne voulez pas itérer:

 #!/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 

Extrait adapté de: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Je pense que c’est assez intelligent.

EDIT: Vous pourriez probablement juste faire:

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

Mais ce dernier ne fonctionne que si le tableau contient des valeurs uniques. Chercher 1 dans “143” donnera des résultats faux positifs.

Bien qu’il y ait eu plusieurs réponses utiles et utiles ici, je n’ai pas trouvé celle qui semblait être la bonne combinaison de performant, multiplateforme et robuste; alors je voulais partager la solution que j’ai écrite pour mon code:

 #!/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 ssortingngs in the array for matching. Must be extremely-unlikely # to appear in the input array or the needle. local delimiter='#!-\8/-!#' # Create a ssortingng with containing every (delimited) element in the array, # and search it for the needle with grep in fixed-ssortingng mode. if printf "${delimiter}%s${delimiter}" "$@" | \ grep --fixed-ssortingngs --quiet "${delimiter}${needle}${delimiter}"; then return 0 fi return 1 } 

Ma version de la technique d’expressions régulières déjà proposée:

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

Ce qui se passe ici est que vous développez le tableau entier des valeurs sockets en charge en mots et en ajoutant une chaîne spécifique, “X-” dans ce cas, à chacun d’eux, et en faisant la même chose à la valeur demandée. Si celui-ci est bien contenu dans le tableau, alors la chaîne résultante correspondra au plus à l’un des jetons résultants, ou aucun du tout. Dans ce dernier cas, le || l’opérateur déclenche et vous savez que vous avez affaire à une valeur non prise en charge. Avant tout cela, la valeur demandée est supprimée de tous les espaces de début et de fin par une manipulation de chaîne de caractères standard.

Je pense que c’est propre et élégant, même si je ne suis pas certain de la performance si votre éventail de valeurs sockets en charge est particulièrement élevé.

En développant la réponse de Sean DiSanti, je pense que ce qui suit est une solution simple et élégante qui évite d’avoir à passer en boucle sur le tableau et à ne pas donner de faux positifs en raison de correspondances partielles.

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

Qui peut être appelé ainsi:

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

Une combinaison de réponses de Beorn Harris et de loentar donne un autre test intéressant:

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

Celui-ci n’utilise pas de fonctions supplémentaires, ne remplace pas les tests et ajoute une protection supplémentaire contre les fausses correspondances occasionnelles en utilisant un code de contrôle comme délimiteur.

Un peu tard, mais vous pourriez utiliser ceci:

 #!/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