Vérifier si un tableau Bash contient une valeur

dans Bash, Quelle est la façon la plus simple de tester si un tableau contient une certaine valeur?

Modifier : Avec l'aide de l'réponses et les 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 suis pas sûr que ce soit la meilleure solution, mais ça semble marcher.

292
demandé sur codeforester 2010-09-10 19:31:33

30 réponses

il y a exemple de code qui montre comment remplacer une chaîne 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, alors la valeur cible existe dans la chaîne d'origine.

la solution simple (mais potentiellement plus longue) consiste simplement à itérer tout le tableau et à vérifier chaque élément individuellement. C'est ce que j'ai généralement faire parce qu'il est facile à mettre en œuvre et vous pouvez l'envelopper dans une fonction (Voir cette information sur passer un tableau à une fonction ).

9
répondu bta 2012-06-26 18:41:05

ci-dessous est une petite fonction pour atteindre cet objectif. La chaîne de recherche est le premier argument et le reste sont les éléments du tableau:

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

un essai de cette fonction pourrait ressembler à:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
291
répondu patrik 2017-08-11 08:54:30

cette approche a l'avantage de ne pas devoir boucler tous les éléments (du moins pas explicitement). Mais depuis array_to_string_internal() dans "151970920 tableau.c fait encore des boucles sur les éléments du tableau et les concaténate en une chaîne, ce n'est probablement pas plus efficace que les solutions de 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 dans un élément de tableau avec des espaces, il va donner de faux positifs. Par exemple

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

le regex verra Jack comme étant dans le tableau même s'il ne l'est pas. Vous devrez donc changer IFS et les caractères de séparation sur votre regex si vous voulez 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
262
répondu Keegan 2016-10-26 14:10:59
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
54
répondu ghostdog74 2010-09-11 01:35:33
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

pour cordes:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
31
répondu Scott 2010-09-10 15:42:23

en général, je viens de l'utiliser:

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

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

15
répondu Sean DiSanti 2015-01-19 20:01:48

si vous avez besoin de performances, vous ne voulez pas boucler 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 indice de ce tableau. C'est-à-dire: il mappe chaque élément de tableau dans son index dans le tableau:

make_index () {
  local index_name=
  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
}

, Alors vous pouvez l'utiliser comme ceci:

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

Et de tester l'appartenance de la sorte:

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

ou aussi:

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

notez que cette solution fait la bonne chose même s'il y a des espaces dans la valeur testée ou dans les valeurs du tableau.

en bonus, vous obtenez également l'index de la valeur dans le tableau avec:

echo "<< ${myarray_index[$member]} >> is the index of $member"
15
répondu LeoRochael 2016-02-15 13:37:07

Voici une petite contribution:

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

Note: cette façon ne distingue pas le cas "deux mots", mais ce n'est pas nécessaire dans la question.

10
répondu hornetbzz 2011-02-23 03:39:03
containsElement () { for e in "${@:2}"; do [[ "$e" = "" ]] && return 0; done; return 1; }

maintenant gère correctement les matrices vides.

9
répondu Yann 2012-06-26 17:39:38

une autre doublure sans fonction:

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

Merci @Qwerty pour les mises en garde concernant les espaces!

fonction correspondante:

find_in_array() {
  local word=
  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"
9
répondu estani 2018-03-19 14:48:31

Une solution en ligne

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

explication

la déclaration printf imprime chaque élément du tableau sur une ligne séparée.

la déclaration grep utilise les caractères spéciaux ^ et $ pour trouver une ligne qui contient exactement le modèle donné comme mypattern (pas plus, pas moins).


l'Utilisation de la

pour mettre dans un if ... then déclaration:

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

j'ai ajouté un drapeau -q à l'expression grep pour qu'elle n'imprime pas les correspondances; elle traitera simplement l'existence d'une correspondance comme "vrai"."

6
répondu JellicleCat 2018-09-17 20:57:39

si vous voulez faire un test rapide et sale pour voir s'il vaut la peine itérer sur l'ensemble du tableau pour obtenir une correspondance précise, Bash peut traiter les tableaux comme des scalaires. Tester pour une correspondance dans le scalaire, si aucun, puis sauter la boucle permet de gagner du temps. Évidemment, 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

cette option affichera" Checking "et"Match". Avec array=(word "two words" something) il ne sortira que"vérification". Avec array=(word "two widgets" something) il n'y aura pas de sortie.

5
répondu Dennis Williamson 2010-09-10 16:07:20
a=(b c d)

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

si vous préférez, vous pouvez utiliser les options équivalentes:

--fixed-strings --quiet --line-regexp --null-data
5
répondu Steven Penny 2015-09-18 00:44:15

ça marche 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: /q/how-to-pass-an-array-to-a-bash-function-69748/"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
4
répondu Chris Prince 2016-11-18 01:03:39

:

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

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 d'attribuer P séparément, plutôt que d'utiliser l'expression directement à l'intérieur [[ ]] est de maintenir la compatibilité pour bash 4)

3
répondu Beorn Harris 2013-09-12 07:33:18

j'écris généralement ces types d'utilités pour opérer sur le nom de la variable, plutôt que la valeur de la variable, principalement parce que bash ne peut pas autrement passer des variables par référence.

Voici une version qui fonctionne avec le nom du tableau:

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

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

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

avec ceci, l'exemple de question devient:

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

etc.

2
répondu Barry Kelly 2013-09-28 23:56:56

utilisant grep et printf

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

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; 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 aucun problème avec les délimiteurs et les espaces.

2
répondu Qwerty 2016-11-01 12:55:22

après avoir répondu, j'ai lu une autre réponse qui m'a particulièrement plu, mais elle était imparfaite et rétrograde. J'ai été inspiré et voici deux nouvelles approches que je vois viables.

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

utilisant grep et printf :

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

utilisant for :

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

pour les résultats not_found ajouter || <run_your_if_notfound_command_here>

1
répondu Qwerty 2016-11-01 13:00:21

voilà ce que je pense.

je préfère ne pas utiliser une bash pour boucle si je peux l'éviter, car cela prend du temps à courir. Si quelque chose doit faire une boucle, que ce soit 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"\ ' "${[@]}") )
  return $(( 1 - 0${_arr[]} ))
}

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

la fonction ne contient pas de boucles explicites, bien qu'en interne bash pas à travers le tableau d'entrée afin de peupler printf . Le format printf utilise %q pour s'assurer que les données d'entrée sont échappées afin qu'elles puissent être utilisées en toute sécurité comme des 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 est intégrée à bash, donc il n'y a pas de tuyaux externes qui vous entraînent vers le bas, même dans l'expansion de commande.

Et si vous n'aimez pas utiliser eval ... vous êtes libre d'utiliser une autre approche. :- )

1
répondu ghoti 2017-02-10 13:04:02

en combinant quelques-unes des idées présentées ici, vous pouvez faire une élégante si statment sans boucles qui fait mot exact correspond .

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

cela ne se déclenche pas sur word ou val , seuls les mots entiers correspondent. Il se cassera si chaque valeur du tableau contient plusieurs mots.

1
répondu Ecker00 2017-09-09 19:04:05

empruntant à Dennis Williamson 's answer , la solution suivante combine des tableaux, des citations sans shell et des expressions régulières pour éviter le besoin de: itérer sur des boucles; utiliser des pipes ou d'autres sous-processus; ou utiliser 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 de Bash pour correspondre avec une version stringifiée du contenu du tableau. Il y a six importante les étapes pour s'assurer que l'expression régulière match ne peut pas être dupé par des combinaisons intelligentes de valeurs dans le tableau:

  1. construisez la chaîne de comparaison en utilisant printf de Bash, %q . Les guillemets permettront de s'assurer que les caractères spéciaux deviennent "sans risque pour les coquillages" en s'échappant avec un antislash \ .
  2. choisissez un caractère spécial pour servir de délimiteur de valeur. Le délimiteur doit être l'un des les caractères spéciaux qui seront échappés en utilisant %q ; c'est la seule façon de garantir que les valeurs dans le tableau ne peuvent pas être construites de manière intelligente pour tromper l'expression régulière match. Je choisis virgule , parce que ce caractère est le plus sûr quand eval'd ou abusé d'une manière autrement 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 comme délimiteur. En utilisant la virgule comme exemple, j'ai utilisé ,,%q comme argument pour printf . Ceci est important parce que deux instances du caractère spécial ne peuvent apparaître l'une à côté de l'autre que lorsqu'elles apparaissent comme le délimiteur; toutes les autres instances du caractère spécial seront échappées.
  4. ajoute deux instances de suivi du délimiteur à la chaîne, pour permettre des correspondances avec le dernier élément du tableau. Ainsi , au lieu de comparer avec ${array_str} , comparer contre ${array_str},, .
  5. si la chaîne cible que vous recherchez est fournie par une variable utilisateur, vous devez échapper à toutes les instances du caractère spécial avec un antislash. Dans le cas contraire, la correspondance d'expression régulière devient vulnérable au fait d'être dupé par des éléments de tableau astucieux.
  6. effectuer un match D'expression régulière Bash contre la chaîne.
1
répondu Dejay Clayton 2017-09-13 20:48:52

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

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

Et la version longue, qui je pense est beaucoup plus facile sur les yeux.

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

function arrayContains() {
        local haystack=${!1}
        local needle=""
        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
0
répondu robert 2016-03-06 21:02:00

j'ai eu le cas que je devais vérifier si un ID était contenu dans une liste d'ID générés par un autre script / commande. Pour moi travaillé le texte suivant:

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

vous pouvez également raccourcir / compacter comme ceci:

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

dans mon cas, j'exécutais jq pour filtrer quelques JSON pour une liste d'IDs et j'ai dû vérifier plus tard si mon ID était dans cette liste et cela a fonctionné le mieux pour moi. Il ne fonctionnera pas pour les tableaux créés manuellement du type LIST=("1" "2" "4") mais pour avec sortie de script newline séparée.


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

0
répondu E. Körner 2018-03-02 15:57:02

un petit ajout à la réponse de @ghostdog74 sur l'utilisation de case logique pour vérifier que le tableau contient une valeur particulière:

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

Ou avec extglob option est activée, vous pouvez le faire comme ceci:

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

nous pouvons aussi le faire avec if déclaration:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
0
répondu Aleksandr Podkutin 2018-09-20 08:41:13

le code suivant vérifie si une valeur donnée est dans le tableau et renvoie son offset à base 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 se fait sur les valeurs complètes, donc la valeur de réglage=" trois " ne correspond pas.

-1
répondu Sven Rieke 2013-01-09 12:45:10

cela pourrait être intéressant d'étudier 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 qu'il est très 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 un faux positif, il me semble.

-1
répondu Sigg3.net 2013-07-02 09:40:17

bien qu'il y ait eu plusieurs grandes et utiles réponses ici, je n'en ai pas trouvé une qui semblait être la bonne combinaison de performant, cross-plateforme, et robuste; donc je voulais partager la solution que j'ai écrit pour mon code:

#!/bin/bash

# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item () 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=""
    shift

    # Separates strings in the array for matching. Must be extremely-unlikely
    # to appear in the input array or the needle.
    local delimiter='#!-/-!#'

    # 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
}
-1
répondu Will 2015-10-13 19:40:06

ma version de la technique des expressions régulières qui a déjà été suggérée:

values=(foo bar)
requestedValue=bar

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

ce qui se passe ici, c'est que vous étendez tout le tableau des valeurs supportées en mots et que vous préparez une chaîne spécifique," X - " dans ce cas, à chacun d'eux, et que vous faites la même chose à la valeur demandée. Si celui-ci est effectivement contenu dans le tableau, alors la chaîne résultante sera au plus égale à l'un des jetons résultants, ou Aucun Au contraire. Dans le dernier cas, l'opérateur || déclenche et vous savez que vous avez affaire à une valeur non supportée. Avant tout cela, la valeur demandée est dépouillée de tout espace principal et arrière par la manipulation standard de la chaîne de caractères shell.

c'est propre et élégant, je crois, mais je ne suis pas trop sûr de comment performant il peut être si votre tableau de valeurs soutenues est particulièrement large.

-1
répondu jmpp 2016-02-28 03:07:18

en S'appuyant sur la réponse ci-dessus de Sean DiSanti, je pense que ce qui suit est une solution simple et élégante qui évite d'avoir à boucler sur le tableau et ne donnera pas de faux positifs en raison de matches partiels

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

que l'on peut appeler ainsi:

$ haystack=("needle1" "needle2" "aneedle" "spaced needle")
$ is_in_array "needle" "${haystack[@]}"
$ echo $?
1
$ is_in_array "needle1" "${haystack[@]}"
$ echo $?
0
-1
répondu Dylan 2017-12-08 15:00:40

Une combinaison de réponses par Beorn Harris et loentar donne un de plus intéressant sur une doublure de test:

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

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

-2
répondu Sergey Ushakov 2014-07-15 03:41:23