Vérifiez si une variable contient seulement ce que je veux, et rien d'autre

--platforms un script pour créer un logiciel à partir de sources, et il y a une option --platforms . Je voudrais permettre à l'user de sélectionner plusieurs éléments, mais je ne sais pas comment les empêcher de faire une erreur.

Exemple:

 read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? " if [[ -n "`echo $REPLY | grep 'win\|mac\|linux32\|linux64\|all`" ]] ; then echo "ok" else echo "not ok" fi 

Si l'user répond linux32 , il devrait être OK (et il l'est)

Si l'user répond linux32,mac , il devrait être OK (et il l'est)

Si l'user répond lulz , cela ne devrait PAS être OK (et ce n'est pas le cas)

Si l'user répond linux32,lulz , il ne devrait PAS être OK (et c'est, c'est mon problème)

Je me demandais si vous saviez un moyen de permettre à l'user d'entrer ce qu'ils veulent séparés par des virgules, mais seulement si c'est l'une des options que le script offre, donc dans ce cas linux32 linux64 mac win all .

Peut-être avec le case il y a un moyen d'autoriser plusieurs inputs, ou peut-être append un elif $REPLY contains anything else than what we want . Une autre idée, pourrait être utilisée? Je ne peux pas me comprendre comment faire cela.

Une version simplifiée / améliorée de la réponse d' arnefm :

 read -p 'Enter a comma-separated list of platforms to build for [win/mac/linux32/linux64/all]: ' input IFS=',' read -a options <<< "$input" shopt -s extglob for option in "${options[@]}"; do case "$option" in win|mac|linux@(32|64)|all) buildcommand="${buildcommand:+$buildcommand,}$option" buildvar=1;; *) printf 'Invalid option "%s" ignored.\n' "$option" >&2;; esac done IFS=',' read -a options <<< "$buildcommand" for option in "${options[@]}"; do if [[ $option == 'all' ]]; then buildcommand='all' break fi done if (( !buildvar )); then echo 'Incorrect input. Default build selected.' >&2 buildcommand='default' fi 

read peut briser l'input en mots et stocker le résultat dans un tableau. Définissez la variable IFS sur le caractère séparateur de mots (il doit s'agir d'un seul caractère, pas d'une string – si la valeur de l' IFS contient plusieurs caractères, chaque caractère est un séparateur de mots).

 IFS=, read -a platforms 

Ensuite, vérifiez chaque élément du tableau par rapport à l'set des plates-forms sockets en charge.

 for p in "${platforms[@]}"; do case "$p" in win|mac|linux32|linux64) :;; all) platforms=(win mac linux32 linux64);; *) printf 1>&2 "Unsupported platform: %s\n" "$p"; return 2;; esac done 

Vous pouvez également comparer l'set des plates-forms en une seule fois. Ceci est plus pratique si vous ne souhaitez pas coder en dur l'set des plateforms sockets en charge dans le code de vérification¹.

 supported_platforms=(win mac linux32 linux64) IFS=, read -a platforms bad_platform_names=($(comm -23 <(printf '%s\n' all "${platforms[@]}" | sort -u) \ <(printf '%s\n' "${supported_platforms[@]}" | sort -u))) if [[ ${#bad_platform_names[@]} -ne 0 ]]; then printf "Unsupported platform: %s\n" "${bad_platform_names[@]}" exit 1 fi if printf '%s\n' "${platforms[@]}" | grep -qx all; then platforms=("${supported_platforms[@]}") fi 

Une approche différente serait d'inviter les plates-forms un par un en utilisant le builtin select .

¹ Bien sûr, vous pouvez le faire en pure bash si vous préférez.

Essaye ça!

 buildvar=0 read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? " input IFS=',' read -a options <<< "$input" for option in "${options[@]}" do case "$option" in linux32) buildcommand="linux32" && buildvar=1 ;; linux64) [ $buildvar == 1 ] && buildcommand="$buildcommand",linux64 || buildcommand="linux64" && buildvar=1 ;; mac) [ $buildvar == 1 ] && buildcommand="$buildcommand",mac || buildcommand="mac" && buildvar=1 ;; win) [ $buildvar == 1 ] && buildcommand="$buildcommand",win || buildcommand="win" && buildvar=1 ;; all) buildcommand="all" && buildvar=1 ;; *) echo "'$option' was ignored." ;; esac done [ $buildvar == "0" ] && echo "Incorrect input. Default build selected." && buildcommand="default" echo "=> $buildcommand" 

Cela vous requestra une list d'options séparées par des virgules. Il splita ensuite cette list en un tableau et parcourra les éléments en vérifiant chaque élément séparément, puis combinera tous les «bons» éléments en une seule variable.

J'espère que cela t'aides!

Vous pouvez lire une seule ligne d'input avec sed et traduire le délimiteur en newlines comme:

 % sed 'y/,/\n/;q' /dev/tty > this,is,a,single,line ##OUTPUT this is a single line 

Parce que sed écrit le résultat sur stdout en tant que file text, le fait de le suivre avec un grep e x plicit est facile et se produit dans un seul stream. Et, en fait, vous pouvez utiliser sed comme un tee intelligent si vous utilisez sa fonction write pour classr; et, si vous w des périphériques descripteurs, vous pouvez split sa sortie en fonction des règles que vous définissez.

Une fonction qui invite à entrer et à ne sortir qu'une list délimitée par une nouvelle ligne d'arguments acceptables vers la sortie stdout et la sortie erronée vers stderr peut ressembler à ceci:

 _accept_prompt() ( . /dev/fd/0 IFS=${delim-,} _prompt "$@" >&2 { _read_split "$@" | err=all _grep_ok "$@" | sed '1{$d}' >&2 } 3>&1 | _grep_ok "$@" ) <<\HELPERS _prompt() { cat ; printf ' : ' } <<-PROMPT Choose from : $(printf "'%s' " "$@") Enter a '$IFS'-delimited selection below... PROMPT _read_split() { y="y/${IFS}/\n/" sed -ne "H;x;s/^/Invalid input IGNORED:/;${y};p;x" \ -ne "/all/s/.*/$*/;${y};w /dev/fd/3" -ne q } </dev/tty _grep_ok() { grep -${err+v}xF "$(printf '%s\n' "$@" $err)" } HELPERS 

J'ai scindé ceci dans des fonctions d'aide plus espérables appelées de façon plus descriptive au lieu des commentaires et les ai attachés à la fonction principale. Donc, le stream se produit dans les premières lignes. J'espérais clarifier cela.

_read_split sort deux stream – >&1 et >&3 . _grep_ok reprend le premier avec $err défini et écrit sur >&2 toutes les lignes contenues dans son input qui ne sont pas parmi les parameters de position de _accept_prompt .

_grep_ok également en même time le deuxième stream – >&3 et écrit dans son input >&1 stdout toutes les lignes qui sont parmi les parameters de position de _accept_prompt .

Exécuter:

 % _accept_prompt this is the list of acceptable parameters ###PROMPT Choose from : 'this' 'is' 'the' 'list' 'of' 'acceptable' 'parameters' Enter a ','-delimited selection below... ###INPUT : all,invalid ###STDOUT this is the list of acceptable parameters ###STDERR Invalid input IGNORED: invalid 

Vous pouvez modifier le délimiteur de virgule par défaut sur l'invocation comme:

 delim=? _accept_prompt $args 

Je présume que vous avez déjà un moyen d'get les variables depuis l'input, puisque vous avez mentionné une option –platforms. Vous pouvez utiliser des expressions régulières pour correspondre aux conditions valides.

 regex='^(mac|win|linux(32|64)|all)(,(mac|win|linux(32|64)|all))*$' if [[ $1 =~ $regex ]]; then echo "ok" else echo "not ok" fi 

Cela devrait satisfaire toutes les exigences:

 $ ./stack.sh linux32 ok $ ./stack.sh linux32,mac ok $ ./stack.sh lulz not ok $ ./stack.sh linux32,lulz not ok 

Certes, cela n'empêche pas les gens d'utiliser "tout, tout, tout", mais cela peut au less être réduit au fur et à mesure de l'exécution de choses plus tard.

Les avantages de cette méthode incluent la possibilité de faire une boucle jusqu'à ce que l'user le fasse correctement, si vous utilisez la command de lecture que vous avez dans le premier post. Les inconvénients include être un peu déroutant pour jeter un coup d'œil.

 regex='^(mac|win|linux(32|64)|all)(,(mac|win|linux(32|64)|all))*$' while ! [[ $REPLY =~ $regex ]]; do read -p "For what platforms do you wish to build [mac/win/linux32/linux64/all] ? " done 

La méthode alternative proposée par Gilles incite les users à entrer le less possible en leur demandant les options.

 available_platforms="mac win linux32 linux64" positive() { printf "%s (Answer y for yes or anything else for no) " "$1" read answer if [ "$answer" != "y" ] && [ "$answer" != "Y" ]; then return 1 fi } if positive "Do you want to build for all platforms [$available_platforms]?"; then build_platforms="$available_platforms" else for platform in $available_platforms; do if positive "Do you want to build for [$platform]?"; then build_platforms="${build_platforms}${platform} " fi done fi printf "build_platforms: %s\n" "$build_platforms"