Vérifiez si un tableau Bash contient une valeur

443

Dans Bash, quelle est la manière la 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.

Paolo Tedesco
la source

Réponses:

458

Cette approche a l'avantage de ne pas avoir à boucler sur tous les éléments (du moins pas explicitement). Mais comme array_to_string_internal()dans array.c boucle toujours les éléments du tableau et les concatène en une chaîne, ce n'est probablement pas plus efficace que les solutions de bouclage proposées, mais c'est plus lisible.

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

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

Le regex verra "Jack" comme étant dans le tableau même s'il ne l'est pas. Vous devrez donc changer IFSet les caractères de séparation sur votre expression régulière si vous voulez toujours utiliser cette solution, comme celle-ci

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

Cela affichera "false".

Évidemment, cela peut également être utilisé comme une déclaration de test, ce qui permet de l'exprimer comme une ligne

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
la source
1
J'ai ajouté un espace au début de la première correspondance de valeur d'expression régulière, afin qu'il ne corresponde qu'au mot, pas à quelque chose se terminant par le mot. Fonctionne très bien. Cependant, je ne comprends pas pourquoi vous utilisez la deuxième condition, la première ne fonctionnerait-elle pas bien seule?
JStrahl
1
@AwQiruiGuo Je ne suis pas sûr de suivre. Parlez-vous de tableaux avec des littéraux en dollars? Si c'est le cas, assurez-vous simplement d'échapper aux dollars de la valeur que vous comparez avec les barres obliques inverses.
Keegan du
10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda
3
Shellcheck se plaint de cette solution, SC2199 et SC2076. Je ne pouvais pas corriger les avertissements sans casser la fonctionnalité. Avez-vous d'autres réflexions à ce sujet que la désactivation du shellcheck pour cette ligne?
Ali Essam
4
SC2076 est facile à corriger, il suffit de supprimer les guillemets doubles dans le if. Je ne pense pas qu'il existe un moyen d'éviter SC2199 avec cette approche. Vous devez explicitement boucler le tableau, comme indiqué dans certaines des autres solutions, ou ignorer l'avertissement.
Keegan
388

Voici une petite fonction pour y parvenir. La chaîne de recherche est le premier argument et les autres sont 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 à:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
patrik
la source
5
Fonctionne bien! Je dois juste de se rappeler de passer le tableau comme avec les guillemets: "${array[@]}". Sinon, les éléments contenant des espaces briseront la fonctionnalité.
Juve
26
Agréable. Je l'appellerais elementIn () car il vérifie si le premier argument est contenu dans le second. containsElements () semble que le tableau irait en premier. Pour les débutants comme moi, un exemple d'utilisation d'une fonction qui n'écrit pas dans stdout dans une instruction "if" serait utile: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Merci pour votre aide!
GlenPeterson
5
@Bluz la construction && est un opérateur booléen AND. L'utilisation d'opérateurs booléens crée une instruction booléenne. La logique booléenne indique que l'instruction entière ne peut être vraie que si les instructions avant et après le && sont évaluées à true. Ceci est utilisé comme un raccourci insted of et if block.Le test est évalué et s'il est faux, il n'est pas nécessaire d'évaluer le retour car il n'est pas pertinent pour l'ensemble de l'instruction une fois que le test a échoué et n'est donc pas exécuté. Si le test réussit, la réussite de l'instruction booléenne DOIT être déterminée afin que le code soit exécuté.
Peteches
4
@James par convention, le code de réussite dans bash est "0" et l'erreur est tout> = 1. C'est pourquoi il renvoie 0 en cas de succès. :)
tftd
11
@Stelios shiftdécale la liste des arguments de 1 vers la gauche (en supprimant le premier argument) et forsans initère implicitement sur la liste des arguments.
Christian
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
ghostdog74
la source
69
Notez que cela n'itère pas sur chaque élément du tableau séparément ... au lieu de cela, il concatène simplement le tableau et correspond à "deux" en tant que sous-chaîne. Cela pourrait provoquer un comportement indésirable si l'on teste si le mot exact "deux" est un élément du tableau.
MartyMacGyver
Je pensais que cela allait fonctionner pour moi en comparant les types de fichiers, mais j'ai constaté que lorsque les compteurs augmentaient, il comptait trop de valeurs ... boo!
Mike Q
17
faux! Raison: case "${myarray[@]}" in *"t"*) echo "found" ;; esacsorties:found
Sergej Jevsejev
@MartyMacGyver, pourriez-vous s'il vous plaît regarder mon ajout à cette réponse stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin
46
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Pour les cordes:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
Scott
la source
Cela dit, vous pouvez utiliser une boucle indexée for et éviter d'être tué lorsqu'un élément de tableau contient IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb
@Matt: Vous devez être prudent ${#}car Bash prend en charge les tableaux clairsemés.
pause jusqu'à nouvel ordre.
@Paolo, si votre tableau contient un espace, comparez-le simplement comme une chaîne. un espace est également une chaîne.
Scott
@Paolo: Vous pouvez en faire une fonction, mais les tableaux ne peuvent pas être passés comme arguments, vous devrez donc le traiter comme un global.
pause jusqu'à nouvel ordre.
Dennis a raison. Extrait du manuel de référence bash: "Si le mot est entre guillemets, ... $ {nom [@]} développe chaque élément de nom en un mot séparé"
mkb
37

Solution en une ligne

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

Explication

L' printfinstruction imprime chaque élément du tableau sur une ligne distincte.

L' grepinstruction utilise les caractères spéciaux ^et $pour trouver une ligne qui contient exactement le modèle donné comme mypattern(ni plus, ni moins).


Usage

Pour mettre cela dans une if ... thendéclaration:

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

J'ai ajouté un -qdrapeau à l' grepexpression pour qu'elle n'imprime pas les correspondances; cela traitera simplement l'existence d'une correspondance comme "vraie".

JellicleCat
la source
Bonne solution! Sur GNU grep, il y a aussi "--line-regexp" qui pourrait remplacer "-P" et les ^ et $ dans le motif: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

Si vous avez besoin de performances, vous ne voulez pas parcourir votre tableau entier à chaque fois que vous effectuez une recherche.

Dans ce cas, vous pouvez créer un tableau associatif (table de hachage ou dictionnaire) qui représente un index de ce tableau. C'est-à-dire qu'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=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

Et testez l'adhésion comme ceci:

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"
LeoRochael
la source
+1 pour l'idée que vous devez utiliser un tableau associatif. Je pense que le code pour make_indexest un peu plus artificiel en raison de l'indirection; vous auriez pu utiliser un nom de tableau fixe avec un code beaucoup plus simple.
musiphil
17

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.

Sean DiSanti
la source
Certes, c'est certainement la solution la plus simple - devrait être une réponse marquée à mon avis. Ayez au moins mon vote positif! [:
ToVine
2
Cela ne fonctionnera pas pour des aiguilles similaires. Par exemple,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Très vrai. se joindre à un délimiteur qui n'est présent dans aucun élément et l'ajouter à l'aiguille aiderait à cela. Peut-être quelque chose comme ... (non testé)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
L'utilisation de grep -x éviterait les faux positifs: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher
Peut-être simplement inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)que -x amène grep à essayer de faire correspondre la chaîne d'entrée entière
MI Wright
17

Un autre liner sans fonction:

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

Merci @Qwerty pour les informations concernant les espaces!

fonction correspondante:

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

exemple:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
la source
1
Pourquoi avons-nous besoin d'un sous-shell ici?
codeforester
1
@codeforester c'est vieux ... mais comme il a été écrit, vous en avez besoin pour en sortir, c'est ce que exit 0fait (s'arrête dès que possible s'il est trouvé).
estani
La fin de la doublure doit être || echo not found place de || not foundou le shell essaiera d'exécuter une commande par le nom de not avec un argument trouvé si la valeur demandée n'est pas dans le tableau.
zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Gère désormais correctement les tableaux vides.

Yann
la source
En quoi est-ce différent de la réponse de @ patrik? La seule différence que je vois est "$e" = "$1"(au lieu de "$e" == "$1") qui ressemble à un bug.
CivFan
1
Ce n'est pas. @ patrik a fusionné mon commentaire dans sa réponse d'origine à l'époque (patch # 4). Remarque: "e" == "$1"est syntaxiquement plus clair.
Yann
@CivFan Dans sa forme actuelle, c'est plus court que celui de la réponse de patrik, en raison de l'élégant $ {@: 2} et de l'auto-documentation $ 1. J'ajouterais que la citation n'est pas nécessaire dans [[]].
Hontvári Levente
9

Voici une petite contribution:

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

Remarque: cette façon ne distingue pas le cas "deux mots" mais ce n'est pas obligatoire dans la question.

hornetbzz
la source
Celui-ci m'a beaucoup aidé. Merci!
Ed Manet
La question n'a pas dit explicitement que vous deviez donner la bonne réponse, mais je pense que cela est implicite dans la question ... Le tableau ne contient pas la valeur "deux".
tetsujin
Ce qui précède signalera une correspondance pour «rd».
Noel Yap du
6

Si vous voulez faire un test rapide et sale pour voir s'il vaut la peine d'itérer sur l'ensemble du tableau pour obtenir une correspondance précise, Bash peut traiter les tableaux comme des scalaires. Testez une correspondance dans le scalaire, si aucun, sauter la boucle 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 produira "Vérification" et "Correspondance". Avec array=(word "two words" something)elle ne sortira que "Vérification". Avec array=(word "two widgets" something)il n'y aura pas de sortie.

En pause jusqu'à nouvel ordre.
la source
Pourquoi ne pas simplement remplacer wordspar une expression régulière ^words$qui ne correspond qu'à la chaîne entière, ce qui élimine complètement la nécessité de vérifier chaque élément individuellement?
Dejay Clayton
@DejayClayton: Parce pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]que ne correspondra jamais car il vérifie tout le tableau à la fois comme s'il s'agissait d'un scalaire. Les vérifications individuelles dans ma réponse ne doivent être effectuées que s'il y a une raison de procéder en fonction de la correspondance approximative.
pause jusqu'à nouvel ordre.
Ah, je vois ce que tu essaies de faire. J'ai proposé une variante de réponse plus performante et sécurisée.
Dejay Clayton
6

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
Chris Prince
la source
5
a=(b c d)

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-strings --quiet --line-regexp --null-data
Steven Penny
la source
1
Cela ne fonctionne pas avec BSD-grep sur Mac, car il n'y a pas de --null-data. :(
Will
4

Empruntant à la réponse de Dennis Williamson , la solution suivante combine des tableaux, des citations shell-safe et des expressions régulières pour éviter d'avoir à: itérer sur les boucles; en utilisant 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 chaîne du contenu du tableau. Il existe six étapes importantes pour garantir que la correspondance d'expression régulière ne peut pas être dupée par des combinaisons intelligentes de valeurs dans le tableau:

  1. Construire la chaîne de comparaison à l'aide Bash est construit en printfcoquille citant, %q. Les guillemets shell garantissent 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 s'échapperont lors de l'utilisation %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 la correspondance d'expression régulière. Je choisis une virgule, parce que ce personnage est le plus sûr lorsqu'il est évalué ou utilisé de 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é ,,%qcomme argument pour printf. Ceci est important car deux instances du caractère spécial ne peuvent apparaître l'une à côté de l'autre que lorsqu'elles apparaissent comme délimiteur; toutes les autres instances du caractère spécial seront échappées.
  4. Ajoutez deux instances de fin du délimiteur à la chaîne pour autoriser les correspondances avec le dernier élément du tableau. Ainsi, au lieu de comparer contre ${array_str}, comparez 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 une barre oblique inverse. Sinon, la correspondance d'expression régulière devient vulnérable à être dupée par des éléments de tableau intelligemment conçus.
  6. Effectuez une correspondance d'expression régulière Bash avec la chaîne.
Dejay Clayton
la source
Très intelligent. Je peux voir que la plupart des problèmes potentiels sont évités, mais je voudrais tester pour voir s'il y a des cas d'angle. Aussi, j'aimerais voir un exemple de gestion du point 5. Quelque chose comme printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]peut-être.
pause jusqu'à nouvel ordre.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino
3

Un petit ajout à la réponse de @ ghostdog74 sur l'utilisation de la caselogique 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 l' extgloboption 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 également le faire avec une ifdéclaration:

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

donné :

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

Beorn Harris
la source
j'adore votre utilisation du mot "simple" ici ... 😂
Christian
2

En combinant quelques-unes des idées présentées ici, vous pouvez faire une déclaration élégante 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 se déclenchera pas sur wordou val, seuls les mots entiers correspondront. Il se cassera si chaque valeur de tableau contient plusieurs mots.

Ecker00
la source
1

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 pas autrement transmettre des 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 <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
}

Avec cela, l'exemple de question devient:

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

etc.

Barry Kelly
la source
Quelqu'un peut-il publier un exemple de cela utilisé dans un if, en particulier comment vous passez dans le tableau. J'essaie de vérifier si un argument du script a été passé en traitant les paramètres comme un tableau, mais il ne veut pas fonctionner. params = ("$ @") check = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; puis .... Mais lors de l'exécution du script avec 'asas' comme argument, il continue de dire asas: commande introuvable. : /
Steve Childs
1

Utilisation de grepetprintf

Formatez chaque membre du tableau sur une nouvelle ligne, puis greples 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élimètres et les espaces.

Qwerty
la source
1

Vérification sur une ligne sans «grep» et boucles

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

Cette approche n'utilise ni utilitaires externes comme grepni boucles.

Ce qui se passe ici, c'est:

  • nous utilisons un matcher de sous-chaîne générique pour trouver notre élément dans le tableau qui est concaténé dans une chaîne;
  • nous supprimons les faux positifs possibles en enfermant notre élément de recherche entre une paire de délimiteurs;
  • nous utilisons un caractère non imprimable comme délimiteur, par sécurité;
  • nous obtenons que notre délimiteur soit également utilisé pour la concaténation de tableau par remplacement temporaire de la IFSvaleur de la variable;
  • nous rendons ce IFSremplacement de valeur temporaire en évaluant notre expression conditionnelle dans un sous-shell (à l'intérieur d'une paire de parenthèses)
Sergey Ushakov
la source
Éliminez DLM. Utilisez IFS directement.
Robin A. Meade
C'est la meilleure réponse. Je l'ai tellement aimé, j'ai écrit une fonction en utilisant cette technique .
Robin A. Meade
1

Utilisation de l'expansion des paramètres:

$ {paramètre: + mot} Si le paramètre est nul ou non défini, rien n'est substitué, sinon l'expansion de mot est substituée.

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done
Gabriel Laden
la source
${myarray[hello]:+_}fonctionne très bien pour les tableaux associés, mais pas pour les tableaux indexés habituels. La question est de trouver une valeur dans un tableau, pas de vérifier si la clé d'un tableau associatif existe.
Eric
0

Après avoir répondu, j'ai lu une autre réponse qui m'a particulièrement plu, mais elle était imparfaite et dévalorisée. 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 grepet 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 non trouvés, ajoutez || <run_your_if_notfound_command_here>

Qwerty
la source
0

Voici mon point de vue à ce sujet.

Je préfère ne pas utiliser de boucle bash for si je peux l'éviter, car cela prend du temps à s'exécuter. Si quelque chose doit boucler, que ce soit quelque chose qui a été écrit dans un langage de niveau inférieur à 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 indices sont dérivés des valeurs du tableau en entrée. (Notez que les tableaux associatifs sont disponibles dans bash 4 et supérieur, donc cette fonction ne fonctionnera pas dans les versions antérieures de bash.) Nous définissons$IFS pour éviter la division des mots sur les espaces.

La fonction ne contient pas de boucles explicites, bien que bash en interne parcourt le tableau d'entrée afin de remplir printf. Le format printf utilise %qpour garantir que les données d'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 intégré à bash, donc il n'y a pas de canaux externes vous entraînant, même dans l'extension de commande.

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

ghoti
la source
Que faire si le tableau contient des crochets?
gniourf_gniourf
@gniourf_gniourf - semble être bien si les crochets sont équilibrés, mais je peux voir que c'est un problème si votre tableau inclut des valeurs avec des crochets non équilibrés. Dans ce cas, j'invoquerais l' evalinstruction à la fin de la réponse. :)
ghoti
Ce n'est pas que je n'aime pas eval(je n'ai rien contre, contrairement à la plupart des gens qui pleurent evalest mal, surtout sans comprendre ce qui est mal à ce sujet). Juste que votre commande est cassée. Peut - être au %qlieu de %sserait mieux.
gniourf_gniourf
1
@gniourf_gniourf: Je voulais seulement dire le bit "une autre approche" (et je suis totalement avec vous eval, évidemment), mais vous avez absolument raison, %qsemble aider, sans casser quoi que ce soit d'autre que je puisse voir. (Je ne savais pas que% q échapperait également aux crochets.) Un autre problème que j'ai vu et corrigé concernait les espaces. Avec a=(one "two " three), similaire au problème de Keegan: non seulement a array_contains a "two "obtenu un faux négatif, mais a array_contains a twoobtenu un faux positif. Assez facile à réparer en réglant IFS.
ghoti
Concernant les espaces blancs, n'est-ce pas parce qu'il manque des guillemets? il rompt également avec les caractères glob. Je pense que vous voulez cela à la place:, eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )et vous pouvez abandonner le local IFS=. Il y a toujours un problème avec les champs vides dans le tableau, car Bash refusera de créer une clé vide dans un tableau associatif. Un moyen rapide et hacky de le corriger consiste à ajouter un caractère factice, par exemple x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )et return $(( 1 - 0${_arr[x$2]} )).
gniourf_gniourf
-1

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 développez la totalité du tableau des valeurs prises en charge en mots et ajoutez une chaîne spécifique, "X-" dans ce cas, à chacun d'eux, et faites de même à la valeur demandée. Si celui-ci est en effet contenu dans le tableau, alors la chaîne résultante correspondra tout au plus à l'un des jetons résultants, ou pas du tout au contraire. Dans ce dernier cas, le || déclencheurs d'opérateur 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 blancs de début et de fin par une manipulation de chaîne shell standard.

C'est propre et élégant, je pense, bien que je ne sois pas trop sûr de la performance que cela peut être si votre tableau de valeurs prises en charge est particulièrement grand.

jmpp
la source
-1

Voici mon point de 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 je pense est beaucoup plus agréable 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
robert
la source
Je n'utilise pas bash depuis si longtemps maintenant que j'ai du mal à comprendre les réponses, ou même ce que j'ai écrit moi-même :) Je n'arrive pas à croire que cette question soit toujours active après tout ce temps :)
Paolo Tedesco
Et alors test_arr=("hello" "world" "two words")?
Qwerty
-1

J'ai eu le cas où 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, a travaillé ce qui suit:

# 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 le raccourcir / le 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 du JSON pour une liste d'ID et j'ai dû vérifier plus tard si mon ID était dans cette liste et cela a fonctionné le mieux pour moi. Cela ne fonctionnera pas pour les tableaux créés manuellement du type LIST=("1" "2" "4")mais pour les sorties de script séparées par des sauts de ligne.


PS: je n'ai pas pu commenter une réponse car je suis relativement nouveau ...

E. Körner
la source
-2

Le code suivant vérifie si une valeur donnée est dans le tableau et renvoie son décalage de 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 est effectuée sur les valeurs complètes, donc la définition de VALUE = "trois" ne correspondrait pas.

Sven Rieke
la source
-2

Cela pourrait valoir la peine d'être étudié si vous ne voulez pas répéter:

#!/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 simplement 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. Rechercher 1 sur "143" donnera des faux positifs, je pense.

Sigg3.net
la source
-2

Un peu tard, mais vous pouvez 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
Coder-256
la source
-2

Je suis venu avec celui-ci, qui ne fonctionne que dans zsh, mais je pense que l'approche générale est agréable.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

Vous ne retirez votre motif de chaque élément que s'il commence ${arr[@]/#pattern/}ou se termine ${arr[@]/%pattern/}avec lui. Ces deux substitutions fonctionnent en bash, mais les deux en même temps${arr[@]/#%pattern/} ne fonctionnent qu'en zsh.

Si le tableau modifié est égal à l'original, il ne contient pas l'élément.

Éditer:

Celui-ci fonctionne en bash:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

Après la substitution, il compare la longueur des deux tableaux. Obly si le tableau contient l'élément, la substitution le supprimera complètement et le nombre sera différent.

spelufo
la source