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


  • Français


  • 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.expconfigurez-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

    Programmation et développement

    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.

    Source

    Houssen Moshinaly

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code