Tester si l'élément est dans un tableau en bash


17

Existe-t-il un bon moyen de vérifier si un tableau a un élément dans bash (mieux que de boucler)?

Sinon, existe-t-il un autre moyen de vérifier si un nombre ou une chaîne est égal à l'un des constantes prédéfinies?

Réponses:


24

Dans Bash 4, vous pouvez utiliser des tableaux associatifs:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

Pour configurer initialement le tableau, vous pouvez également effectuer des affectations directes:

array[foo]=1
array[bar]=1
# etc.

ou de cette façon:

array=([foo]=1 [bar]=1 [baz]=1)

En fait, le test [[]] ne fonctionne pas dans le cas où la valeur est vide. Par exemple, "array ['test'] = ''". Dans ce cas, la clé 'test' existe et vous pouvez la voir répertoriée avec $ {! Array [@]}, mais "[[$ {array ['test']}]]; echo $?" fait écho à 1, pas à 0.
haridsv

1
${array[$test1]}est simple mais a un problème: cela ne fonctionnera pas si vous utilisez set -udans vos scripts (ce qui est recommandé), car vous obtiendriez une "variable non liée".
tokland

@tokland: Qui le recommande? Certainement pas.
pause jusqu'à nouvel ordre.

@DennisWilliamson: Ok, certaines personnes le recommandent, mais je pense que ce serait bien d'avoir une solution qui fonctionne quelle que soit la valeur de ces drapeaux.
tokland


10

Il est une vieille question, mais je pense que ce qui est la solution la plus simple n'a pas encore apparu: test ${array[key]+_}. Exemple:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

Les sorties:

a is set
b is set

Pour voir comment cela fonctionne, vérifiez cela .


2
Le manuel d'informations vous recommande d'utiliser envpour éviter les ambiguïtés dans les alias, progs et autres fonctions qui peuvent avoir adopté le nom "test". Comme ci-dessus env test ${xs[a]+_} && echo "a is set". Vous pouvez également obtenir cette fonctionnalité en utilisant des crochets doubles, la même astuce que la vérification de null:[[ ! -z "${xs[b]+_}" ]] && echo "b is set"
A.Danischewski

Vous pouvez également utiliser le plus simple encore[[ ${xs[b]+set} ]]
Arne L.

5

Il existe un moyen de tester si un élément d'un tableau associatif existe (non défini), c'est différent de vide:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

Ensuite, utilisez-le:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi

juste une note: déclarer -A ne fonctionne pas sur bash 3.2.39 (debian lenny), mais il fonctionne sur bash 4.1.5 (debian squeeze)
Paweł Polewicz

Les tableaux associatifs ont été introduits dans Bash 4.
Diego F. Durán

1
notez que if ! some_check then return 1= some_check. Donc: isNotSet() { [[ ... ]] }. Consultez ma solution ci-dessous, vous pouvez le faire en une simple vérification.
Tokland

3

Vous pouvez voir si une entrée est présente en redirigeant le contenu du tableau vers grep.

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

Vous pouvez également obtenir l'index d'une entrée avec grep -n, qui renvoie le numéro de ligne d'une correspondance (n'oubliez pas de soustraire 1 pour obtenir un index de base zéro). Ce sera assez rapide, sauf pour les très grands tableaux.

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

explication:

  • $( ... ) revient à utiliser des astuces pour capturer la sortie d'une commande dans une variable
  • printf affiche mydata un élément par ligne
  • (toutes les citations sont nécessaires, et au @lieu de *. cela évite de diviser "bonjour le monde" en 2 lignes)
  • greprecherche la chaîne exacte: ^et fait $correspondre le début et la fin de la ligne
  • grep -n retourne la ligne #, en forme de 4: bonjour le monde
  • grep -m 1 trouve la première correspondance uniquement
  • cut extrait uniquement le numéro de ligne
  • soustrayez 1 du numéro de ligne renvoyé.

Vous pouvez bien sûr replier la soustraction dans la commande. Mais testez ensuite -1 pour manquer:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) fait l'arithmétique entière

1

Je ne pense pas que vous puissiez le faire correctement sans boucle, sauf si vous avez des données très limitées dans le tableau.

Voici une variante simple, cela dirait correctement qu'il "Super User"existe dans le tableau. Mais cela voudrait aussi dire que "uper Use"c'est dans le tableau.

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

Le problème est qu'il n'y a pas de moyen facile d'ajouter les ancres (auxquelles je peux penser) en plus de parcourir le tableau. À moins que vous ne puissiez les ajouter avant de les mettre dans le tableau ...


C'est une bonne solution quand les constantes sont alphanumériques, cependant (avec grep "\b$FINDME\b"). Peut probablement fonctionner avec des constantes non alphanumériques qui n'ont pas d'espaces, avec "(^| )$FINDME(\$| )"(ou quelque chose comme ça ... Je n'ai jamais pu apprendre quelle saveur de grep regexp utilise.)
Tgr

1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi

1
Pouvez-vous expliquer pourquoi vous proposez cette approche? OP a demandé s'il y avait un moyen de le faire sans parcourir le tableau , c'est ce que vous faites in_array. Cheers
bertieb

Eh bien, au moins cette boucle est capsulée dans une fonction, ce qui pourrait être suffisant pour de nombreux cas (avec de petits ensembles de données), et ne nécessite pas bash 4+. Devrait probablement ${ARRAY[@]}être utilisé.
Tobias
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.