Apprenez à attendre en écrivant et en automatisant un jeu simple

En essayant d’automatiser mon flux de travail, j’ai découvert un utilitaire de configuration qui défiait toute automatisation significative. Il s’agissait d’un processus Java qui ne prenait pas en charge un programme d’installation silencieux ou ne prenait pas en charge stdin
, et comportait un ensemble d’invites incohérent. Ansible’s expect
module n’était pas adapté à cette tâche. Mais j’ai trouvé que le expect
commande était juste l’outil pour le travail.
Mon parcours pour apprendre Expect signifiait apprendre un peu de Tcl. Maintenant que j’ai les connaissances nécessaires pour créer des programmes simples, je peux mieux apprendre à programmer dans Expect. J’ai pensé qu’il serait amusant d’écrire un article qui démontre les fonctionnalités intéressantes de ce vénérable utilitaire.
Cet article va au-delà du format de jeu simple typique. Je prévois d’utiliser des parties d’Expect pour créer le jeu lui-même. Ensuite, je démontre la puissance réelle d’Expect avec un script séparé pour automatiser le jeu.
Cet exercice de programmation montre plusieurs exemples de programmation classiques de variables, d’entrées, de sorties, d’évaluations conditionnelles et de boucles.
Installer Attendre
Pour les systèmes basés sur Linux, utilisez :
$ sudo dnf install expect
$ which expect
/bin/expect
J’ai découvert que ma version d’Expect était incluse dans le système d’exploitation de base de macOS :
$ which expect
/usr/bin/expect
Sur macOS, vous pouvez également charger une version légèrement plus récente à l’aide de brew :
$ brew install expect
$ which expect
/usr/local/bin/expect
Devinez le nombre dans Expect
Le jeu de devinettes utilisant Expect n’est pas si différent de la Tcl de base que j’ai utilisée dans mon article précédent.
Toutes les choses en Tcl sont des chaînes, y compris les valeurs des variables. Les lignes de code sont mieux contenues par des accolades (au lieu d’essayer d’utiliser la continuation de ligne). Les crochets sont utilisés pour la substitution de commande. La substitution de commande est utile pour dériver des valeurs d’autres fonctions. Il peut être utilisé directement comme entrée si nécessaire. Vous pouvez voir tout cela dans le script suivant.
Créer un nouveau fichier de jeu numgame.exp
configurez-le pour qu’il soit exécutable, puis saisissez le script ci-dessous :
#!/usr/bin/expect
proc used_time {start} {
return [expr [clock seconds] - $start]
}
set num [expr round(rand()*100)]
set starttime [clock seconds]
set guess -1
set count 0
send "Guess a number between 1 and 100\n"
while { $guess != $num } {
incr count
send "==> "
expect {
-re "^(\[0-9]+)\n" {
send "Read in: $expect_out(1,string)\n"
set guess $expect_out(1,string)
}
-re "^(.*)\n" {
send "Invalid entry: $expect_out(1,string) "
}
}
if { $guess < $num } {
send "Too small, try again\n"
} elseif { $guess > $num } {
send "Too large, try again\n"
} else {
send "That's right!\n"
}
}
set used [used_time $starttime]
send "You guessed value $num after $count tries and $used elapsed seconds\n"
En utilisant proc
établit une définition de fonction (ou de procédure). Celui-ci est composé du nom de la fonction, suivi d’une liste contenant les paramètres (1 paramètre {start}
) puis suivi du corps de la fonction. L’instruction return montre un bon exemple de substitution de commandes Tcl imbriquées. Le set
les instructions définissent des variables. Les deux premiers utilisent la substitution de commande pour stocker un nombre aléatoire et l’heure système actuelle en secondes.
Le while
boucle et la logique if-elseif-else devraient être familières. Notez à nouveau le placement particulier des accolades pour aider à regrouper plusieurs chaînes de commande sans avoir besoin de continuer la ligne.
La grande différence que vous voyez ici (par rapport au programme Tcl précédent) est l’utilisation des fonctions expect
et send
plutôt que d’utiliser puts
et gets
. En utilisant expect
et send
forment le cœur de l’automatisation du programme Expect. Dans ce cas, vous utilisez ces fonctions pour automatiser un humain à un terminal. Plus tard, vous pouvez automatiser un vrai programme. En utilisant le send
commande dans ce contexte n’est pas beaucoup plus que l’impression d’informations à l’écran. Le expect
la commande est un peu plus complexe.
Le expect
La commande peut prendre différentes formes en fonction de la complexité de vos besoins de traitement. L’utilisation typique consiste en une ou plusieurs paires modèle-action telles que :
expect "pattern1" {action1} "pattern2" {action2}
Des besoins plus complexes peuvent placer plusieurs paires d’actions de modèle dans des accolades éventuellement préfixées avec des options qui modifient la logique de traitement. Le formulaire que j’ai utilisé ci-dessus encapsule plusieurs paires modèle-action. Il utilise l’option -re
pour appliquer le traitement regex (au lieu du traitement glob) au modèle. Il suit cela avec des accolades encapsulant une ou plusieurs instructions à exécuter. J’ai défini deux modèles ci-dessus. Le premier est Est destiné à correspondre à une chaîne de 1 ou plusieurs nombres :
"^(\[0-9]+)\n"
Le deuxième modèle est conçu pour correspondre à tout ce qui n’est pas une chaîne de nombres :
"^(.*)\n"
Notez que cette utilisation de expect
est exécuté à plusieurs reprises à partir d’un while
déclaration. Il s’agit d’une approche parfaitement valide pour la lecture d’entrées multiples. Dans l’automatisation, je montre une légère variation de Expect qui fait l’itération pour vous.
Finalement, le $expect_out
variable est un tableau utilisé par expect
pour conserver les résultats de son traitement. Dans ce cas, la variable $expect_out(1,string)
contient le premier motif capturé de la regex.
Exécutez le jeu
Il ne devrait pas y avoir de surprise ici :
$ ./numgame.exp
Guess a number between 1 and 100
==> Too small, try again
==> 100
Read in: 100
Too large, try again
==> 50
Read in: 50
Too small, try again
==> 75
Read in: 75
Too small, try again
==> 85
Read in: 85
Too large, try again
==> 80
Read in: 80
Too small, try again
==> 82
Read in: 82
That's right!
You guessed value 82 after 8 tries and 43 elapsed seconds
Une différence que vous remarquerez peut-être est l’impatience présente cette version. Si vous hésitez assez longtemps, attendez-vous à des délais d’attente avec une entrée invalide. Il vous invite ensuite à nouveau. Ceci est différent de gets
qui attend indéfiniment. Le expect
timeout est une fonctionnalité configurable. Il aide à gérer les programmes bloqués ou lors d’une sortie inattendue.
Automatisez le jeu dans Expect
Pour cet exemple, le script d’automatisation Expect doit se trouver dans le même dossier que votre numgame.exp
scénario. Créer le automate.exp
fichier, rendez-le exécutable, ouvrez votre éditeur et entrez ce qui suit :
#!/usr/bin/expect
spawn ./numgame.exp
set guess [expr round(rand()*100)]
set min 0
set max 100
puts "I'm starting to guess using the number $guess"
expect {
-re "==> " {
send "$guess\n"
expect {
"Too small" {
set min $guess
set guess [expr ($max+$min)/2]
}
"Too large" {
set max $guess
set guess [expr ($max+$min)/2]
}
-re "value (\[0-9]+) after (\[0-9]+) tries and (\[0-9]+)" {
set tries $expect_out(2,string)
set secs $expect_out(3,string)
}
}
exp_continue
}
"elapsed seconds"
}
puts "I finished your game in about $secs seconds using $tries tries"
Le spawn
La fonction exécute le programme que vous souhaitez automatiser. Il prend la commande sous forme de chaînes séparées suivies des arguments à lui transmettre. Je mets le nombre initial à deviner, et le vrai plaisir commence. Le expect
est considérablement plus compliqué et illustre la puissance de cet utilitaire. Notez qu’il n’y a pas d’instruction en boucle ici pour parcourir les invites. Étant donné que mon jeu comporte des invites prévisibles, je peux demander expect
pour faire un peu plus de traitement pour moi. L’extérieur expect
tente de faire correspondre l’invite de saisie du jeu `==>` . Voyant cela, il utilise send
à deviner, puis utilise un autre expect
pour comprendre les résultats de la conjecture. En fonction de la sortie, les variables sont ajustées et calculées pour configurer la prochaine estimation. Lorsque l’invite `==>` correspond, le exp_continue
déclaration est invoquée. Cela provoque l’extérieur expect
à réévaluer. Donc une boucle ici n’est plus nécessaire.
Ce traitement d’entrée repose sur un autre comportement du traitement d’Expect. Attendez-vous à mettre en mémoire tampon la sortie du terminal jusqu’à ce qu’elle corresponde à un modèle. Cette mise en mémoire tampon inclut toute fin de ligne intégrée et autres caractères non imprimables. Ceci est différent de la correspondance de ligne regex typique à laquelle vous êtes habitué avec Awk et Perl. Lorsqu’un motif est trouvé, tout ce qui vient après la correspondance reste dans le tampon. Il est disponible pour la prochaine tentative de match. J’ai exploité cela pour terminer proprement l’extérieur expect
déclaration:
-re "value (\[0-9]+) after (\[0-9]+) tries and (\[0-9]+)"
Vous pouvez voir que le motif interne correspond à la bonne estimation et ne consomme pas tous les caractères imprimés par le jeu. La toute dernière partie de la chaîne (secondes écoulées) est toujours mise en mémoire tampon après la supposition réussie. Lors de la prochaine évaluation de l’extérieur expect
, cette chaîne est mise en correspondance à partir du tampon pour se terminer proprement (aucune action n’est fournie). Maintenant, pour la partie amusante, exécutons l’automatisation complète :
$ ./automate.exp
spawn ./numgame.exp
I'm starting to guess with the number 99
Guess a number between 1 and 100
==> 99
Read in: 99
Too large, try again
==> 49
Read in: 49
Too small, try again
==> 74
Read in: 74
Too large, try again
==> 61
Read in: 61
Too small, try again
==> 67
Read in: 67
That's right!
You guessed value 67 after 5 tries and 0 elapsed seconds
I finished your game in about 0 seconds using 5 tries
Ouah! Mon efficacité à deviner les nombres a considérablement augmenté grâce à l’automatisation ! Quelques essais ont abouti à 5 à 8 suppositions en moyenne. Il a également toujours terminé en moins de 1 seconde. Maintenant que ce plaisir embêtant et chronophage peut être expédié si rapidement, je n’ai aucune excuse pour retarder d’autres tâches plus importantes comme travailler sur mes projets d’amélioration de la maison 😛
Ne jamais arrêter d’apprendre
Cet article était un peu long mais en valait la peine. Le jeu de devinettes de nombres offrait une bonne base pour démontrer un exemple plus intéressant de traitement Expect. J’ai beaucoup appris de l’exercice et j’ai pu terminer mon automatisation du travail avec succès. J’espère que vous avez trouvé cet exemple de programmation intéressant et qu’il vous aide à atteindre vos objectifs d’automatisation.