Comment vérifier si une variable existe dans une liste dans BASH

138

J'essaye d'écrire un script en bash qui vérifie la validité d'une entrée d'utilisateur.
Je veux faire correspondre l'entrée (disons la variable x) à une liste de valeurs valides.

ce que je suis venu avec pour le moment est:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

Ma question est de savoir s'il existe un moyen plus simple de le faire,
quelque chose comme list.contains(x)pour la plupart des langages de programmation.

Ajout:
Dites que la liste est:

list="11 22 33"

mon code ne fera écho au message que pour ces valeurs car il listest traité comme un tableau et non comme une chaîne, toutes les manipulations de chaîne seront validées 1alors que je voudrais qu'il échoue.

Ofir Farchy
la source

Réponses:

143
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

ou créez une fonction:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

pour l'utiliser:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
la source
37
devrait être[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Peut donner un faux positif si l'entrée de l'utilisateur contient des caractères spéciaux d'expression régulière, par exemplex=.
glenn jackman
4
Cela donnera pas de faux positifs / négatifs: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
6
une solution plus succincte: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. c'est correct en supposant que l'espace est le séparateur et $xne contient pas d'espace
Tianren Liu
2
Je pense qu'il est préférable d'utiliser une fonction "est dans" isIn()afin que vous puissiez d'abord écrire l'élément dans les paramètres. Vous pouvez également echoutiliser quelque chose au lieu d'utiliser exitcomme ceci: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Vous pouvez donc utiliser la fonction de cette façon:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
34

Matvey a raison, mais vous devriez citer $ x et considérer tout type d '"espaces" (par exemple, nouvelle ligne) avec

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

donc, ie

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

fin alors

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

ou

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Notez que vous devez utiliser ""en cas de variables:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
la source
3
Cette solution fonctionne même si elle $itemcontient des caractères spéciaux, comme .(mais la $listvariable doit probablement être citée dans le test). Et la fonction peut être définie encore plus simple: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
skozin
ne fonctionne pas dans des cas comme: list="aa bb xx cc ff"etx="aa bb"
creativeChips
Bonjour. J'essaie d'apprendre les principes de base de bashscript et je voulais savoir comment vous pouvez analyser une chaîne de liste, en liste, et faire la vérification d'existence dans cette liste. Je peux comprendre que vous vous sépariez en utilisant l'espace mais je ne pouvais pas comprendre pleinement l'expression. Pouvez - vous me fournir les mots - clés à Google les fondamentaux de cette expression: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Ou, si vous avez le temps, je serais heureux d'entendre votre explication pour cette expression. Remarque: je suppose que c'est une expression regex (commence par ^ etc) mais je ne sais pas ce que signifie = ~. J'adorerais donc une explication générale: P
Ali Yılmaz
28

La solution la plus simple à mon humble avis est de préfixer et d'ajouter la chaîne d'origine avec un espace et de vérifier par rapport à une expression régulière avec [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

ce ne sera pas un faux positif sur les valeurs avec des valeurs contenant l'aiguille comme sous-chaîne, par exemple avec une botte de foin foo barbaz.

(Le concept est volé sans vergogne de la hasClass()-Méthode de JQuery )

blaimi
la source
4
si le séparateur n'est pas un espace, la solution est plus évidente. cela peut aussi être fait sans regex: haystack="foo:bar"et[[ ":$haystack:" = *:$needle:* ]]
phiphi
25

que diriez-vous

echo $list | grep -w $x

vous pouvez vérifier la sortie ou la $?ligne ci-dessus pour prendre la décision.

grep -w vérifie les modèles de mots entiers.

Kent
la source
1
cela ne validerait-il pas "val" si "value" est valide (c'est-à-dire dans la liste) puisque c'est sa sous
Ofir Farchy
Oui, ce serait le cas, tout comme la réponse acceptée. @glennjackman donne une solution de travail.
f.ardelian
3
vous pouvez utiliser grep -q pour faire en sorte que grep soit silencieux
Amir
cela accepterait également des correspondances partielles, ce qui peut ne pas être ce que l'utilisateur veut
carnicer
3
@carnicer puis utilisez simplement grep -w $ x pour avoir une correspondance exacte
user1297406
18

Vous pouvez également utiliser (* wildcards) en dehors d'une instruction case, si vous utilisez des doubles crochets:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
la source
3
Simple et élégant, +1
João Pereira
14
Cela donnera un faux positif si "My" est une sous-chaîne d'une autre chaîne, par exemple string = 'MyCommand string'.
Luke Lee
Il convient de noter que les jokers ( *My*) doivent être sur le côté droit du test.
Jacob Vanus
8

Si votre liste de valeurs doit être codée en dur dans le script, son utilisation est assez simple à tester case. Voici un petit exemple que vous pouvez adapter à vos besoins:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Si la liste est une variable de tableau au moment de l'exécution, l'une des autres réponses est probablement mieux adaptée.

Toby Speight
la source
7

Si la liste est fixe dans le script, j'aime ce qui suit le plus:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Ensuite, utilisez validate "$x"pour tester si cela $xest autorisé.

Si vous voulez un one-liner et que vous ne vous souciez pas des espaces dans les noms d'éléments, vous pouvez utiliser ceci (notez -wau lieu de -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Remarques:

  • Ceci est shconforme à POSIX .
  • validaten'accepte pas les sous-chaînes (supprimez l' -xoption de grep si vous le souhaitez).
  • validateinterprète son argument comme une chaîne fixe, pas comme une expression régulière (supprimez l' -Foption de grep si vous le souhaitez).

Exemple de code pour exercer la fonction:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done
Søren Løvborg
la source
6

Pensez à exploiter les clés des tableaux associatifs . Je suppose que cela surpasse à la fois la correspondance regex / modèle et la mise en boucle, bien que je ne l'ai pas profilé.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Production:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )
RubyMardiDONO
la source
2
... et vous pouvez simplifier que le remplacement (et ne pas compter sur echo -n) avec l' utilisation créative des paramètres: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
Existe-t-il un moyen simple de créer un tel tableau si je n'ai qu'une (longue) liste comme `list =" un deux trois xyz ... "?
Ott Toomet
5

Je trouve qu'il est plus facile d'utiliser le formulaire echo $LIST | xargs -n1 echo | grep $VALUEcomme illustré ci-dessous:

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Cela fonctionne pour une liste séparée par des espaces, mais vous pouvez l'adapter à n'importe quel autre délimiteur (comme :) en procédant comme suit:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Notez que les "sont nécessaires pour que le test fonctionne.

Sébastien Pierre
la source
Cela fera LIST="SOMEITEM1 ITEM2"revenir vrai même si ce ITEM1n'est pas dedans
Ofir Farchy
Bonne prise, j'ai mis à jour l'exemple avec grep -e pour exclure une correspondance partielle.
Sébastien Pierre
Je crois qu'il y a un extra `à la fin de la déclaration" si ". La forme correcte est: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Je pensais ajouter ma solution à la liste.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE
SamWN
la source
1

Exemples

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

dans la liste

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"
Brad Parks
la source
1

En supposant que la variable TARGET ne peut être que 'binomiale' ou 'régression', alors ce qui suit ferait:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

Vous pouvez ajouter plus de chaînes dans la liste en les séparant par un | (pipe) caractère.

L'avantage d'utiliser egrep est que vous pouvez facilement ajouter une insensibilité à la casse (-i), ou vérifier des scénarios plus complexes avec une expression régulière.

Tagar
la source
1

C'est presque votre proposition originale mais presque une 1-ligne. Pas si compliqué que d'autres réponses valides, et pas selon les versions de bash (peut fonctionner avec d'anciennes bashes).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Je suppose que ma proposition peut encore être améliorée, à la fois en longueur et en style.

carnicer
la source
0

Si ce n'est pas trop long; vous pouvez simplement les enchaîner entre les égalités le long d'une comparaison OU logique comme ceci.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

J'ai eu ce problème exact et bien que ce qui précède soit moche, ce qui se passe est plus évident que les autres solutions généralisées.

Kelly Trinh
la source