Je veux créer un script qui vous invite à entrer un nombre compris entre 0 et 100, puis vous attribue une note basée sur ce nombre.
Je voudrais que dans bash.
PS3='Please enter your choice: '
(Something here)
do
case $
"0-59")
echo "F"
;;
"60-69")
echo "D"
;;
"70-79")
echo "C"
;;
"Quit")
break
;;
*) echo invalid option;;
esac
done
Comme vous l'avez vu, ce problème admet des solutions moyennement longues et quelque peu répétitives mais très lisibles ( terdon's et AB's bash réponses), ainsi que celles qui sont très courts mais non intuitifs et beaucoup moins auto-documentés (Tim's python et bash réponses et celles de Glenn Jackman réponse Perl ). Toutes ces approches sont précieuses.
Vous pouvez également résoudre ce problème avec du code au milieu du continuum entre compacité et lisibilité. Cette approche est presque aussi lisible que les solutions plus longues, avec une longueur plus proche des petites solutions ésotériques.
#!/usr/bin/env bash
read -erp 'Enter numeric grade (q to quit): '
case $REPLY in [qQ]) exit;; esac
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; exit; }
done
echo "Grade out of range."
Dans cette solution bash, j'ai inclus des lignes vides pour améliorer la lisibilité, mais vous pouvez les supprimer si vous le souhaitez encore plus court.
Les lignes vides incluses ne sont en réalité que légèrement plus courtes que ne variante compactée, encore assez lisible sur la solution bash de A.B. . Ses principaux avantages par rapport à cette méthode sont les suivants:
((
))
).Ces trois avantages découlent du fait que cette méthode utilise les entrées de l'utilisateur sous forme de données numériques plutôt qu'en examinant manuellement les chiffres qui le composent.
-e
) et n'interprétez pas \
comme un caractère d'échappement (-r
).-r
avec read
, à moins que vous ne sachiez que vous devez laisser l'utilisateur fournir des échappements \
.q
ou Q
, quittez.declare -A
). Remplissez-le avec la note numérique la plus élevée associée à chaque note.((
))
, il n'est pas nécessaire d'étendre les noms de variable avec $
. (Dans la plupart des autres situations, si vous souhaitez utiliser la valeur d'une variable à la place de son nom, vous devez le faire .)&&
) plutôt que if
-then
.Comme les autres solutions courtes publiées, ce script ne vérifie pas l'entrée avant de supposer qu'il s'agit d'un nombre. L'évaluation arithmétique (((
))
) supprime automatiquement les espaces de début et de fin, donc c'est pas de problème, mais:
0
est interprétée comme étant dans octal . Par exemple, le script vous dira que 77 est un C, tandis que 077 est un D. Bien que certains utilisateurs le souhaitent, cela n’est probablement pas le cas et cela peut créer une confusion.Pour ces raisons, vous voudrez peut-être utiliser quelque chose comme ce script développé, qui vérifie que la saisie est bonne et comprend certaines autres améliorations.
#!/usr/bin/env bash
shopt -s extglob
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
case $REPLY in # allow leading/trailing spaces, but not octal (e.g. "03")
*( )@([1-9]*([0-9])|+(0))*( )) ;;
*( )[qQ]?([uU][iI][tT])*( )) exit;;
*) echo "I don't understand that number."; continue;;
esac
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
C'est toujours une solution assez compacte.
Les points clés de ce script développé sont:
if [[ ! $response =~ ^[0-9]*$ ]] ...
, je montre donc une autre façon, qui sacrifie un peu de brièveté mais est plus robuste, permettant à l'utilisateur de saisir des espaces de début et de fin et refusant d'autoriser une expression pouvant être ou non destiné à être octal (sauf si c'est zéro).case
avec étendu globbing au lieu de [[
avec le =~
correspondance d'expression régulière opérateur (comme dans réponse de terdon ). J'ai fait cela pour montrer que (et comment) cela peut aussi être fait de cette façon. Les globes et les expressions rationnelles sont deux façons de spécifier des modèles qui correspondent au texte. L'une ou l'autre méthode convient parfaitement pour cette application.cutoffs
). Il demande des chiffres et donne les notes correspondantes tant que le terminal est disponible et que l'utilisateur ne lui a pas demandé de quitter. À en juger par la variable do
...done
autour du code de votre question, il semble que vous le souhaitiez.q
ou quit
qui respecte la casse.Ce script utilise quelques constructions qui peuvent ne pas être familières aux novices; ils sont détaillés ci-dessous.
continue
Lorsque je veux ignorer le reste du corps de la boucle while
extérieure, j'utilise la commande continue
. Cela le ramène au sommet de la boucle, pour lire plus d'entrées et exécuter une autre itération.
La première fois que je fais cela, la seule boucle dans laquelle je me trouve est la boucle externe while
. Je peux donc appeler continue
sans argument. (Je suis dans une construction case
, mais cela n’affecte pas le fonctionnement de break
ou continue
.)
*) echo "I don't understand that number."; continue;;
La deuxième fois, cependant, je suis dans une boucle intérieure for
qui est elle-même imbriquée dans la boucle extérieure while
. Si j'utilisais continue
sans argument, cela équivaudrait à continue 1
et continuerait la boucle for
intérieure au lieu de la boucle while
extérieure.
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Donc, dans ce cas, j’utilise continue 2
pour que bash trouve et continue la deuxième boucle.
case
étiquettes avec des tachesJe n'utilise pas case
pour déterminer dans quelle lettre bin un nombre entre (comme dans réponse bash de A.B. ). Mais j’utilise case
pour décider si l’opération de l’utilisateur doit être prise en compte:
*( )@([1-9]*([0-9])|+(0))*( )
*( )[qQ]?([uU][iI][tT])*( )
*
Ce sont globes de shell .
)
auquel aucun (
d'ouverture ne correspond, ce qui correspond à la syntaxe de case
pour séparer un modèle des commandes qui s'exécutent lorsqu'il correspond.;;
est la syntaxe de case
pour indiquer la fin des commandes à exécuter pour une correspondance de cas particulier (et qu'aucun cas ultérieur ne doit être testé après les avoir exécutées).L'environnement de shell ordinaire fournit *
pour correspondre à zéro ou plusieurs caractères, ?
pour correspondre exactement à un caractère et les classes/plages de caractères entre [
]
. Mais j'utilise extended globbing , qui va au-delà de cela. La navigation étendue est activée par défaut lors de l'utilisation interactive de bash
, mais elle est désactivée par défaut lors de l'exécution d'un script. La commande shopt -s extglob
en haut du script l’active.
*( )@([1-9]*([0-9])|+(0))*( )
, qui vérifie la saisie numérique , correspond à une séquence de:
*( )
). La construction *(
)
correspond à zéro ou plus du motif dans les parenthèses, ce qui est juste un espace.-e
sur read
permet GNU readline. Cela permet à l'utilisateur de se déplacer d'avant en arrière dans son texte à l'aide des touches fléchées gauche et droite, mais cela a pour effet secondaire d'empêcher généralement la saisie littérale des tabulations.@(
)
) de (|
): [1-9]
) suivi de zéro ou plus (*(
)
) de tout chiffre ([0-9]
).+(
)
) de 0
.*( )
), à nouveau.*( )[qQ]?([uU][iI][tT])*( )
, qui vérifie la commande de sortie , correspond à une séquence de:
*( )
).q
ou Q
([qQ]
).?(
)
) - sur: u
ou U
([uU]
) suivi de i
ou I
([iI]
) suivi de t
ou T
([tT]
).*( )
), à nouveau.Si vous préférez tester l'entrée de l'utilisateur par rapport à une expression régulière plutôt qu'à un glob Shell, vous préférerez peut-être utiliser cette version, qui fonctionne de la même manière, mais utilise [[
et =~
(comme dans terdon's answer ) au lieu de case
et étendu globbing.
#!/usr/bin/env bash
shopt -s nocasematch
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
# allow leading/trailing spaces, but not octal (e.g., "03")
if [[ ! $REPLY =~ ^\ *([1-9][0-9]*|0+)\ *$ ]]; then
[[ $REPLY =~ ^\ *q(uit)?\ *$ ]] && exit
echo "I don't understand that number."; continue
fi
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
Les avantages possibles de cette approche sont les suivants:
Dans ce cas particulier, la syntaxe est un peu plus simple, du moins dans le deuxième motif, où je vérifie la commande quit. En effet, j’ai pu définir l’option nocasematch
Shell, puis toutes les variantes de cas de q
et quit
ont été automatiquement traitées.
C'est ce que fait la commande shopt -s nocasematch
. La commande shopt -s extglob
est omise car la suppression n'est pas utilisée dans cette version.
Les habiletés d'expression régulières sont plus courantes que la maîtrise des extensions de bash.
En ce qui concerne les modèles spécifiés à droite de l'opérateur =~
, voici comment fonctionnent ces expressions régulières.
^\ *([1-9][0-9]*|0+)\ *$
, qui vérifie la saisie numérique , correspond à une séquence de:
^
).*
, postfixe appliqué). Normalement, un espace n'a pas besoin d'être échappé \
- dans une expression régulière, mais ceci est nécessaire avec [[
pour éviter une erreur de syntaxe.(
)
) qui est l'un ou l'autre (|
) parmi: [1-9][0-9]*
: un chiffre différent de zéro ([1-9]
) suivi de zéro ou plus (*
, postfixe appliqué) d'un chiffre quelconque ([0-9]
).0+
: un ou plusieurs (+
, postfixe appliqué) de 0
.\ *
), comme avant.$
).Contrairement aux étiquettes case
, qui correspondent à l'expression entière à tester, =~
renvoie true si une partie quelconque de son expression de gauche correspond au modèle donné en tant qu'expression de droite. C'est pourquoi les ancres ^
et $
, spécifiant le début et la fin de la ligne, sont nécessaires ici et ne correspondent syntaxiquement à rien qui apparaisse dans la méthode avec case
et extglob.
Les parenthèses sont nécessaires pour que ^
et $
soient liés à la disjonction de [1-9][0-9]*
et 0+
. Sinon, ce serait la disjonction de ^[1-9][0-9]*
et 0+$
, et correspondrait à toute entrée commençant par un chiffre différent de zéro ou se terminant par un 0
(ou les deux, pouvant toujours inclure des non-chiffres entre les deux). ).
^\ *q(uit)?\ *$
, qui vérifie la commande de sortie , correspond à une séquence de:
^
).\ *
, voir l'explication ci-dessus).q
. Ou Q
, puisque shopt nocasematch
est activé.?
) - de la sous-chaîne ((
)
): u
, suivi de i
, suivi de t
. Ou, puisque shopt nocasematch
est activé, u
peut être U
; indépendamment, i
peut être I
; et indépendamment, t
peut être T
. (C’est-à-dire que les possibilités sont non limitées à uit
et UIT
.)\ *
).$
).Vous avez déjà l'idée de base. Si vous voulez coder ceci dans bash
(ce qui est un choix raisonnable puisqu'il s'agit du shell par défaut sous Ubuntu et la plupart des autres systèmes Linux), vous ne pouvez pas utiliser case
car il ne comprend pas les plages. À la place, vous pouvez utiliser if
name __/else
name__:
#!/usr/bin/env bash
read -p "Please enter your choice: " response
## If the response given did not consist entirely of digits
if [[ ! $response =~ ^[0-9]*$ ]]
then
## If it was Quit or quit, exit
[[ $response =~ [Qq]uit ]] && exit
## If it wasn't quit or Quit but wasn't a number either,
## print an error message and quit.
echo "Please enter a number between 0 and 100 or \"quit\" to exit" && exit
fi
## Process the other choices
if [ $response -le 59 ]
then
echo "F"
Elif [ $response -le 69 ]
then
echo "D"
Elif [ $response -le 79 ]
then
echo "C"
Elif [ $response -le 89 ]
then
echo "B"
Elif [ $response -le 100 ]
then
echo "A"
Elif [ $response -gt 100 ]
then
echo "Please enter a number between 0 and 100"
exit
fi
#!/bin/bash
while true
do
read -p "Please enter your choice: " choice
case "$choice"
in
[0-9]|[1-5][0-9])
echo "F"
;;
6[0-9])
echo "D"
;;
7[0-9])
echo "C"
;;
8[0-9])
echo "B"
;;
9[0-9]|100)
echo "A"
;;
[Qq])
exit 0
;;
*) echo "Only numbers between 0..100, q for quit"
;;
esac
done
et une version plus compacte (Thx @ EliahKagan ):
#!/usr/bin/env bash
while read -erp 'Enter numeric grade (q to quit): '; do
case $REPLY in
[0-9]|[1-5][0-9]) echo F ;;
6[0-9]) echo D ;;
7[0-9]) echo C ;;
8[0-9]) echo B ;;
9[0-9]|100) echo A ;;
[Qq]) exit ;;
*) echo 'Only numbers between 0..100, q for quit' ;;
esac
done
Toutes les installations Ubuntu ont Python, donc voici un python scénario bon mot. Si vous avez besoin que ce soit en bash, j'ai aussi écrit l'équivalent sous forme de script shell .
print (chr(75-max(5,int('0'+raw_input('Enter the number: ')[:-1]))))
Pour l’exécuter, enregistrez-le dans un fichier (par exemple, grade.py
), puis exécutez-le dans le terminal avec ceci:
python grade.py
Voici ce que vous verrez:
Enter the number: 65
E
Comment cela marche-t-il?
65
.065
.06
.70
.E
name__.E
name__.Voici ma solution semi - ésotérique bash, qui remplit un tableau avec 101 entrées, puis compare les entrées utilisateur à celles-ci. Même pour une utilisation dans le monde réel, c'est raisonnable - si vous avez besoin d'excellentes performances, vous n'utiliserez pas bash, et une centaine (ou plus) de tâches restent rapides. Mais cela cesserait d'être raisonnable s'il était étendu à une gamme beaucoup plus grande (comme un million).
#!/usr/bin/env bash
p(){ for i in `seq $2 $3`; do g[$i]=$1; done; }
p A 90 100; p B 80 89; p C 70 79; p D 60 69; p F 0 59
while read -r n && [[ ! $n =~ ^[qQ] ]]; do echo ${g[$n]}; done
Avantages:
q
name__, quit
name__, ou tout ce qui commence par q
name __/Q
name__.Désavantages:
g
est un tableau indexé à une dimension ). Comme le dit le vieil adage, "Ce n'est pas un bug, c'est une fonctionnalité!" Peut-être.Un peu cool, hein? (Eh bien, je pense que oui.)
p
p opose un tableau indexé numériquement g
of g rades, aux index allant du premier argument au deuxième, avec la valeur (lettre) donnée dans le troisième argument.p
est appelé pour chaque note, afin de définir sa plage numérique.q
(ou Q
name__), vérifiez le tableau g
pour lequel la note correspond au nombre saisi et imprimez cette lettre.Après le rendant dans Python 2 , j'ai décidé de le faire en bash.
#! /bin/bash
read -p "Enter the number: " i
i=0$i
x=$((10#${i::-1}))
printf "\x$(printf %x $((11-($x>5?$x:5)+64)))\n"
Pour l’exécuter, enregistrez-le dans un fichier (par exemple, grade.sh), rendez-le exécutable avec chmod +x grade.sh
, puis exécutez-le avec ./grade.sh
.
Voici ce que vous verrez:
Enter the number: 65
E
Comment cela marche-t-il?
65
.065
(et le 10#
le conserve en base 10).06
.70
.E
name__.E
name__.Et voici ma version awk:
awk '{
if($_ <= 100 && $_ >= 0) {
sub(/^([0-9]|[1-5][0-9])$/, "F", $_);
sub(/^(6[0-9])$/, "D", $_);
sub(/^(7[0-9])$/, "C", $_);
sub(/^(8[0-9])$/, "B", $_);
sub(/^(9[0-9]|100)$/, "A", $_);
print
}
else {
print "Only numbers between 0..100"
}
}' -
ou comme one-liner:
awk '{if($_ <= 100 && $_ >= 0) { sub(/^([0-9]|[1-5][0-9])$/, "F", $_); sub(/^(6[0-9])$/, "D", $_); sub(/^(7[0-9])$/, "C", $_); sub(/^(8[0-9])$/, "B", $_);sub(/^(9[0-9]|100)$/, "A", $_); print} else { print "Only numbers between 0..100"}}' -
Voici une autre réponse "ésotérique"
Perl -E '
print "number: ";
$n = <>;
say qw/A A B C D E F F F F F/[11-($n+1)/10]
if $n=~/^\s*\d/ and 0<=$n and $n<=100
'
Perl -E
: le -E
, comme -e
, permet de transmettre un script en tant qu'argument de ligne de commande. C'est une façon de faire fonctionner les one-liners Perl. Contrairement à -e
, -E
active également toutes les fonctions facultatives (telles que say
, qui est fondamentalement un print
avec une nouvelle ligne de fin.).print "number: ";
: invite l'utilisateur à entrer un nombre.$n = <>;
: enregistrez ce nombre sous le nom $n
.La partie suivante doit être un peu décomposée. qw/string/
évalue une liste en cassant string
à l'espace. Donc, qw/A A B C D E F F F F F/
est en fait cette liste:
0 : A
1 : A
2 : B
3 : C
4 : D
5 : E
6 : F
7 : F
8 : F
9 : F
10 : F
Donc, say qw/A A B C D E F F F F F/[11-($n+1)/10]
est équivalent à
my @F=("A","A","B","C","D","E","F","F","F","F","F");
print "$F[11-($n+1)/10]\n"
Maintenant, Perl permet d'utiliser des indices négatifs pour récupérer des éléments comptant à partir de la fin du tableau. Par exemple, $arrray[-1]
affichera le dernier élément du tableau. De plus, les indices de tableau à virgule flottante (par exemple 10.7) sont automatiquement tronqués au nombre entier immédiatement inférieur (10.7, ou 10.3 ou peu importe ce qui devient 10).
Le résultat de tout cela est que l'index 11-($n+1)/10
correspond toujours à l'élément approprié (grade) du tableau.
Bien que vous ayez demandé une solution bash, je pense qu'en python, cela peut être fait de manière élégante et rapide. Couvrant à la fois les erreurs de manipulation en cas de saisie incorrecte et la "conversion" d'un nombre compris entre 0 et 100 en lettres de A à F (ou tout autre):
#!/usr/bin/env python3
try:
n = int(input("number: ")); n = n if n>0 else ""
print("FEDCBA"[[n>=f for f in [50,60,70,80,90,101]].count(True)])
except:
print("invalid input")
Nous devons d’abord obtenir le numéro de l’utilisateur:
n = int(input("number: "))
Nous testons la validité de ce numéro pour un certain nombre de conditions:
n>=50, n>=60, n>=70, n>=80, n>=90
Pour chacun de ces tests, le résultat sera soit False
, soit True
. Donc (compresser un peu le code):
[n>=f for f in [50,60,70,80,90]].count(True)]
produira un chiffre de 0
à 5
Par la suite, nous pouvons utiliser cette figure comme index pour une chaîne, afin de produire un caractère en sortie, par ex.
"ABCDEF"[3]
affichera "D" (puisque le premier caractère = "A")
Le 101
additionnel à la liste doit générer une erreur (index) au cas où le nombre dépasserait 100
, puisque "ABCDEF"[6]
n'existe pas. Il en va de même pour n = n if n>=0 else ""
, ce qui créera une erreur (valeur) si un nombre inférieur à 0 est entré
Dans ces cas, ainsi que si l'entrée n'est pas un chiffre, le résultat sera:
invalid input
Les tests:
number: 10
F
number: 50
E
number: 60
D
number: 70
C
number: 80
B
number: 90
A
number: 110
invalid input
number: -10
invalid input
number: Monkey
invalid input