Utilisation des pseudo-terminaux pour piloter les programmes interactifs

GNU/Linux Magazine n° 100 | décembre 2007 | Rachid Koucha
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Bien que de moins en moins utilisées, les applications interactives en mode ligne de commandes interagissant avec un opérateur via un terminal sur port série sont encore légions, notamment dans le monde Linux embarqué où les ressources graphiques sont superflues ou d'un coût trop élevé. Parmi ces applications, on peut citer les plus connues :- bash : le shell par défaut de Linux ;- bc : la calculatrice ;- ftp : utilitaire de transfert de fichier ;- telnet : terminal distant.Il est possible de tirer bénéfice de ces utilitaires dans des scripts shell afin d'automatiser certaines tâches comme les tests ou les opérations de maintenance et d'administration système. Par exemple, on pourrait lancer un script qui crée une session telnet sur une machine distante afin de déclencher certaines opérations. Mais, cela n'est pas aussi simple, car un processus interactif, nécessitant l'intervention d'un opérateur pour fonctionner, se prête a priori mal à une automatisation de son déclenchement.Cet article se propose donc de présenter une solution à l'automatisation des programmes interactifs à travers la notion de « pseudo-terminal ».

1. Redirection des entrées et sorties standards d'un processus

Un programme Linux, lorsqu'il est chargé en mémoire pour être exécuté, devient un processus qui est attaché au terminal courant. Par défaut, l'entrée standard (stdin) provient du clavier, tandis que les sorties normales et en erreur standards (stdout et stderr) sont redirigées sur l'écran (Cf. Figure 1).

Figure 1 : Entrées et sorties standards d'un processus Linux

Linux offre la notion de « redirection » des entrées et sorties de sorte à permettre à un processus de lire ses données d'entrée à partir d'une autre source que le clavier du terminal courant et d'afficher ses données de sortie sur une autre destination que l'écran du terminal courant. La puissance de ce mécanisme réside dans le fait que les redirections sont complètement transparentes : le processus lit son entrée standard et affiche sur ses sorties standards sans connaître la nature des périphériques qui se cachent derrière. En d'autres termes, un programme peut être lancé sans modification pour lire tantôt le clavier, tantôt le contenu d'un fichier, tantôt la sortie d'un autre programme (via le mécanisme de pipe). Il en va de même pour ses sorties.

Considérons le programme simple suivant appelé mylogin, qui saisit un nom de login et un mot de passe :

#include <stdio.h>

int main(void)

{

char nom_de_login[150];

char mot_de_passe[150];

// Par défaut stdin, stdout et stderr sont ouverts

fprintf(stdout, "login : ");

if (NULL == fgets(nom_de_login, sizeof(nom_de_login), stdin))

{

  fprintf(stderr, "Pas de nom de login\n");

  return 1;

}

fprintf(stdout, "Mot de passe : ");

if (NULL == fgets(mot_de_passe, sizeof(mot_de_passe), stdin))

{

  fprintf(stderr, "Pas de mot de passe\n");

  return 1;

}

fprintf(stdout, "La saisie est :\n%s%s\n", nom_de_login, mot_de_passe);

return 0;

}

Sous un shell tel que bash, plusieurs solutions sont à disposition pour effectuer les opérations de redirection.

Si on lance le programme simplement, son entrée et ses sorties standards sont respectivement le clavier et l'écran du terminal courant :

$ ./mylogin

login : toto

Mot de passe : foo

La saisie est :

toto

foo

$

Le programme précédent peut être lancé de la manière suivante pour rediriger ce qui est saisi au clavier dans le fichier output.txt :

$ ./mylogin > output.txt

toto

foo

$ cat output.txt

login : Mot de passe : La saisie est :

toto

foo

$

On voit que sans aucune modification du programme mylogin, on a pu le lancer la première fois avec l'entrée standard sur le clavier et la sortie standard sur l'écran et la seconde fois avec l'entrée standard sur le clavier et la sortie standard sur le fichier output.txt.

2. Problèmes d'automatisation d'un programme interactif

Un programme interactif aussi simple que mylogin peut être automatisé. Nous entendons, par automatisation, le remplacement d'un opérateur humain par un programme, tel qu'un script shell. Considérons, par exemple, le fichier input.txt dans lequel on a mis le nom de login et le mot de passe attendus par mylogin :

$ cat input.txt

toto

foo

$

On peut lancer le programme mylogin en injectant le fichier input.txt sur son entrée standard :

$ ./mylogin < input.txt

login : Mot de passe : La saisie est :

toto

foo

$

On a donc remplacé l'opérateur humain par un fichier contenant les entrées attendues par mylogin. Mais, il n'est malheureusement pas possible de généraliser cette méthode à tout programme interactif. En effet, certains sont très élaborés. Typiquement, un programme de saisie de login et mot de passe effectue systématiquement un nettoyage de son entrée standard pour ne pas tenir compte des caractères saisis entre la demande du nom de login et la demande du mot de passe (l'écho des caractères est aussi désactivé pendant la saisie du mot de passe). Pour étayer ces dernières remarques, on peut simuler le nettoyage de l'entrée standard en modifiant mylogin.c de sorte à insérer un appel à fseek() juste avant la saisie du mot de passe :

#include <stdio.h>

int main(void)

{

char nom_de_login[150];

char mot_de_passe[150];

// Par défaut stdin, stdout et stderr sont ouverts

fprintf(stdout, "login : ");

if (NULL == fgets(nom_de_login, sizeof(nom_de_login), stdin))

{

  fprintf(stderr, "Pas de nom de login\n");

  return 1;

}

// Nettoyage de l'entrée standard

fseek(stdin, 0, SEEK_END);

fprintf(stdout, "Mot de passe : ");

if (NULL == fgets(mot_de_passe, sizeof(mot_de_passe), stdin))

{

  fprintf(stderr, "Pas de mot de passe\n");

  return 1;

}

fprintf(stdout, "La saisie est :\n%s%s\n", nom_de_login, mot_de_passe);

return 0;

}

Le programme continue à se comporter comme souhaité quand il interagit avec un opérateur (l'entrée standard est le clavier) :

$ ./mylogin

login : toto

Mot de passe : foo

La saisie est :

toto

foo

Par contre, lorsque l'entrée standard est un fichier, le message d'erreur « Pas de mode de passe » s'affiche pour indiquer qu'aucune donnée n'a été saisie pour le mot de passe. En fait, le deuxième appel à fread() rencontre une fin de fichier qui indique qu'il n'y a plus de données en entrée :

$ ./mylogin < input.txt

Pas de mot de passe

login : Mot de passe :

$

Quand les données du programme sont fournies par l'opérateur, ce dernier attend l'affichage de la chaîne « Mot de passe » avant de saisir le mot de passe. En mode automatique, le fichier input.txt est injecté d'une traite et, par conséquent, sa deuxième ligne se retrouve « nettoyée » par l'appel à fseek(). C'est un cas typique de désynchronisation de l'entrée standard avec le programme.

À travers cet exemple simple, est mis en avant un des nombreux problèmes que l'on peut rencontrer lors du lancement automatique des programmes interactifs. En effet, en plus de la désynchronisation qu'il peut y avoir entre les données en entrée et le programme, ce dernier peut aussi effectuer des opérations de reconfiguration du terminal pour se mettre en mode ligne ou canonique ou tout simplement pour désactiver l'écho des caractères. Si, l'entrée ou les sorties standards ne sont pas des terminaux, mais des fichiers par exemple, alors ces opérations vont échouer et déclencher des erreurs dans le programme.

La notion de « pseudo-terminal » est une solution à ce problème comme nous allons le voir dans la suite.

3. Présentation des pseudo-terminaux

Un pseudo-terminal (communément appelé pty [1]) est une paire de périphériques virtuels en mode caractère : l'un est esclave et l'autre est maître. Un canal bidirectionnel relie ces deux entités. Toute donnée écrite du côté maître se retrouve en sortie du côté esclave. Inversement, toute donnée écrite du côté esclave, se retrouve en sortie du côté maître comme indiqué en figure 2.

Figure 2 : Vue générale d'un pseudo-terminal

La partie esclave se comporte exactement comme un terminal classique dans le sens où tout processus peut l'ouvrir pour en faire son entrée et sa sortie standards. Certains traitements tels que l'écho, le remplacement des carriage returns par des line feeds ou autre peuvent être réalisés sur les données entrant ou sortant sur le ptyesclave.

La partie maître n'est, quant à elle, pas un terminal, mais permet de faire des opérations de lecture de données provenant de la partie esclave et d'écriture de données à destination de la partie esclave.

Dans le monde Unix en général, il existe plusieurs implémentations des pseudo-terminaux. Il y a la version BSD et la version System V. Le monde Linux a retenu la version System V sous l'appellation « Unix 98 pty ». C'est cette dernière qui est recommandée désormais et qui fera donc l'objet de la suite de cet article.

3.1 API des pseudo-terminaux

La mise en œuvre des pseudo-terminaux se fait à l'aide d'une API assez simple :

- posix_openpt

Cette fonction permet de créer la partie maître d'un pseudo-terminal. Elle ouvre le périphérique /dev/ptmx pour obtenir le descripteur de fichier associé à la partie maître du pseudo-terminal.

- grantpt

Après l'appel à posix_openpt(), le descripteur de fichier est passé à grantpt() pour changer les droits d'accès sur la partie esclave du pseudo-terminal : l'identifiant d'utilisateur du périphérique esclave est positionné avec l'identifiant d'utilisateur du processus appelant. Le groupe est positionné à une valeur non spécifiée (par exemple tty) et les droits d'accès sont positionnés à crw--w----.

- unlockpt

Après l'appel à grantpt(), le descripteur de fichier est passé à unlockpt() pour déverrouiller le périphérique esclave.

- ptsname

Après les opérations précédentes, la partie esclave du pseudo-terminal peut être ouverte à l'aide de l'appel système open(). Mais, avant cela, il faut obtenir le nom du périphérique esclave via l'appel à ptsname().

À ces API, il faut ajouter les opérations classiques sur les terminaux telles que tcgetattr(), cfmakeraw()...

Le petit programme suivant appelé mypty met en œuvre l'API pour créer un pseudo-terminal :

#define _XOPEN_SOURCE 600

#include <stdlib.h>

#include <stdio.h>

#include <fcntl.h>

#include <errno.h>

int main(void)

{

int fdm;

int rc;

// Affichage de /dev/pts

system("ls -l /dev/pts");

fdm = posix_openpt(O_RDWR);

if (fdm < 0)

{

  fprintf(stderr, "Erreur %d sur posix_openpt()\n", errno);

  return 1;

}

rc = grantpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur grantpt()\n", errno);

  return 1;

}

rc = unlockpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur unlockpt()\n", errno);

return 1;

}

// Affichage des changements dans /dev/pts

system("ls -l /dev/pts");

printf("Le pseudo-terminal esclave a pour nom : %s\n", ptsname(fdm));

return 0;

} // main

Le programme affiche le contenu du répertoire /dev/pts au début et à la fin de son exécution pour montrer que la création d'un pseudo-terminal ajoute une nouvelle entrée dans le répertoire /dev/pts. Dans l'exemple d'exécution suivant, c'est le pseudo-terminal esclave numéro 4 qui est créé :

$ ./mypty

total 0

crw--w---- 1 koucha tty 136, 0 2007-09-25 13:56 0

crw--w---- 1 koucha tty 136, 1 2007-09-25 13:32 1

crw--w---- 1 koucha tty 136, 2 2007-09-25 12:58 2

crw--w---- 1 koucha tty 136, 3 2007-09-25 07:32 3

total 0

crw--w---- 1 koucha tty 136, 0 2007-09-25 13:56 0

crw--w---- 1 koucha tty 136, 1 2007-09-25 13:32 1

crw--w---- 1 koucha tty 136, 2 2007-09-25 12:58 2

crw--w---- 1 koucha tty 136, 3 2007-09-25 07:32 3

crw--w---- 1 koucha tty 136, 4 2007-09-25 13:56 4

Le pseudo-terminal esclave a pour nom : /dev/pts/4

$

4. Application des pseudo-terminaux

Les pseudo-terminaux sont essentiellement utilisés pour faire croire à un processus qu'il est en interface avec un terminal classique alors qu'il est en communication avec un ou plusieurs processus.

4.1 Communication inter-processus via un pseudo-terminal

Pour mettre en évidence les fonctionnalités d'un pseudo-terminal, on peut modifier le programme mypty en mypty2 comme suit :

#define _XOPEN_SOURCE 600

#include <stdlib.h>

#include <fcntl.h>

#include <errno.h>

#include <unistd.h>

#include <stdio.h>

#define __USE_BSD

#include <termios.h>

int main(void)

{

int fdm, fds, rc;

char input[150];

fdm = posix_openpt(O_RDWR);

if (fdm < 0)

{

  fprintf(stderr, "Erreur %d sur posix_openpt()\n", errno);

  return 1;

}

rc = grantpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur grantpt()\n", errno);

  return 1;

}

rc = unlockpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur unlockpt()\n", errno);

return 1;

}

// Ouverture du PTY esclave

fds = open(ptsname(fdm), O_RDWR);

// Création d'un processus fils

if (fork())

{

  // Code du processus pere

  // Fermeture de la partie esclave du PTY

  close(fds);

  while (1)

  {

    // Saisie operateur (entree standard = terminal)

    write(1, "Entree : ", sizeof("Entree : "));

    rc = read(0, input, sizeof(input));

if (rc > 0)

    {

      // Envoie de la saisie aux processus fils via le PTY

      write(fdm, input, rc);

// Lecture de la reponse du fils dans le PTY

      rc = read(fdm, input, sizeof(input) - 1);

if (rc > 0)

      {

        // Ajout d'une fin de chaine en fin de buffer

        input[rc] = '\0';

        fprintf(stderr, "%s", input);

      }

      else

      {

        break;

      }

    }

    else

    {

      break;

    }

  } // End while

}

else

{

struct termios slave_orig_term_settings;      // Saved terminal settings

struct termios new_term_settings;     // Current terminal settings

// Code du processus fils

  // Fermeture de la partie maitre du PTY

  close(fdm);

  // Sauvegarde des parametre par defaut du PTY esclave

  rc = tcgetattr(fds, &slave_orig_term_settings);

// Positionnement du PTY esclave en mode RAW

  new_term_settings = slave_orig_term_settings;

  cfmakeraw (&new_term_settings);

  tcsetattr (fds, TCSANOW, &new_term_settings);

// Le cote esclave du PTY devient l'entree et les sorties standards du fils

  close(0);     // Fermeture de l'entrée standard (terminal courant)

  close(1);     // Fermeture de la sortie standard (terminal courant)

  close(2);     // Fermeture de la sortie erreur standard (terminal courant)

  dup(fds);     // Le PTY devient l'entree standard (0)

  dup(fds);     // Le PTY devient la sortie standard (1)

  dup(fds);     // Le PTY devient la sortie erreur standard (2)

  while (1)

  {

    rc = read(fds, input, sizeof(input) - 1);

if (rc > 0)

    {

      // Remplacement du retour a la ligne par une fin de chaine

      input[rc - 1] = '\0';

printf("Le fils a recu : '%s'\n", input);

    }

    else

    {

      break;

    }

  } // End while

}

return 0;

} // main

Le programme consiste en deux processus. Le premier (le père) lit une ligne de caractères saisie au clavier et l'envoie sur la partie maître du pty. Le second (le fils) a fait du pty esclave son entrée et ses sorties standards et renvoie, sur sa sortie standard, toute chaîne lue, préfixée par « Le fils a reçu : ». Voici un exemple de lancement :

$ ./mypty2

Entree : azerty

Le fils a recu : 'azerty'

Entree : qwerty

Le fils a recu : 'qwerty'

Entree : pwd

Le fils a recu : 'pwd'

La figure 3 explique le fonctionnement de mypty2 lorsque l'opérateur saisit la chaîne de caractères qwerty :

Figure 3 : Fonctionnement de mypty2

Côté processus fils, on remarquera la configuration du pty esclave via les appels à cfmakeraw() et tcsetattr() de sorte à passer en mode raw (brut en français) pour désactiver les opérations telles que l'écho.

On peut rendre plus générique la modification faite dans mypty2 pour donner la possibilité d'exécuter n'importe quel programme derrière le pty. Dans mypty3, le processus père envoie tout ce qui vient de son entrée standard sur le pty maître et tout ce qui vient du pty maître sur sa sortie standard. Il se conduit simplement comme un relayeur de données. Le processus fils effectue les mêmes opérations que précédemment, mais se généralise pour exécuter un programme interactif quelconque passé en argument. Nous noterons l'appel à setsid() pour faire en sorte que le pty esclave soit le terminal de contrôle du programme exécuté.

#define _XOPEN_SOURCE 600

#include <stdlib.h>

#include <fcntl.h>

#include <errno.h>

#include <unistd.h>

#include <stdio.h>

#define __USE_BSD

#include <termios.h>

#include <sys/select.h>

int main(int ac, char *av[])

{

int fdm, fds;

int rc;

char input[150];

// Contrôle des arguments

if (ac <= 1)

{

  fprintf(stderr, "Usage: %s nom_de_programme\n", av[0]);

  exit(1);

}

fdm = posix_openpt(O_RDWR);

if (fdm < 0)

{

  fprintf(stderr, "Erreur %d sur posix_openpt()\n", errno);

  return 1;

}

rc = grantpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur grantpt()\n", errno);

  return 1;

}

rc = unlockpt(fdm);

if (rc != 0)

{

  fprintf(stderr, "Erreur %d sur unlockpt()\n", errno);

return 1;

}

// Ouverture du PTY esclave

fds = open(ptsname(fdm), O_RDWR);

// Création d'un processus fils

if (fork())

{

fd_set fd_in;

// Code du processus pere

  // Fermeture de la partie esclave du PTY

  close(fds);

  while (1)

{

    // Attente de données de l'entrée standard et du PTY maître

    FD_ZERO(&fd_in);

    FD_SET(0, &fd_in);

    FD_SET(fdm, &fd_in);

    rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);

    switch(rc)

    {

      case -1 : fprintf(stderr, "Erreur %d sur select()\n", errno);

                exit(1);

default :

      {

        // S'il y a des donnees sur l'entree standard

        if (FD_ISSET(0, &fd_in))

        {

          rc = read(0, input, sizeof(input));

if (rc > 0)

          {

            // Envoie des données sur le PTY maitre

            write(fdm, input, rc);

          }

          else

          {

            if (rc < 0)

{

              fprintf(stderr, "Erreur %d sur read entree standard\n", errno);

              exit(1);

            }

          }

        }

        // S'il y a des donnees sur le PTY maitre

        if (FD_ISSET(fdm, &fd_in))

        {

          rc = read(fdm, input, sizeof(input));

if (rc > 0)

          {

            // Envoie des données sur la sortie standard

            write(1, input, rc);

          }

          else

          {

            if (rc < 0)

{

              fprintf(stderr, "Erreur %d sur read PTY maitre\n", errno);

              exit(1);

            }

          }

        }

      }

    } // End switch

  } // End while

}

else

{

struct termios slave_orig_term_settings;      // Saved terminal settings

struct termios new_term_settings;     // Current terminal settings

// Code du processus fils

  // Fermeture de la partie maitre du PTY

  close(fdm);

  // Sauvegarde des parametre par defaut du PTY esclave

  rc = tcgetattr(fds, &slave_orig_term_settings);

// Positionnement du PTY esclave en mode RAW

  new_term_settings = slave_orig_term_settings;

  cfmakeraw (&new_term_settings);

  tcsetattr (fds, TCSANOW, &new_term_settings);

// Le cote esclave du PTY devient l'entree et les sorties standards du fils

  // Fermeture de l'entrée standard (terminal courant)

  close(0);

  // Fermeture de la sortie standard (terminal courant)

  close(1);

  // Fermeture de la sortie erreur standard (terminal courant)

  close(2);   

  // Le PTY devient l'entree standard (0)

  dup(fds);

  // Le PTY devient la sortie standard (1)

  dup(fds);

  // Le PTY devient la sortie erreur standard (2)

  dup(fds);

  // Nouvelle session pour le process courant de sorte

  // a permettre le controle de processus dans le PTY esclave

  // (utile pour le shell)

  setsid();

  // Execution du programme

  execvp(av[1], NULL);

}

return 0;

} // main

Ci-après, on lance mypty3 avec la calculatrice bc à travers le pty :

$ ./mypty3

Usage: ./mypty3 program_name

$

$ ./mypty3 bc

bc 1.06

Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.

This is free software with ABSOLUTELY NO WARRANTY.

For details type `warranty'.

3+6

9

quit

Erreur 5 sur read PTY maitre

$

Il est possible de lancer un shell ou tout autre programme interactif à la place de bc. Cette technique s'applique à nombre de programmes célèbres tels que xterm, telnet, ssh, rlogin, rsh... Par exemple, la figure 4 décrit l'architecture de telnet.

Figure 4 : Description d'une session telnet

Pour faire le parallèle avec les programmes d'exemples précédents, le démon telnetd est le processus père dont l'entrée standard n'est pas un simple terminal, mais un terminal déporté à travers un réseau où s'exécute le client telnet. Le processus fils est un shell bash. Tout ce qui provient du client telnet à travers le réseau est transmis par telnetd sur le pseudo-terminal maître. Tout ce qui provient du shell bash à travers le pseudo-terminal est transmis au client telnet par le réseau.

4.2 Prise de contrôle d'un processus interactif

mypty3 peut être modifié pour rendre le processus père plus intelligent de sorte à lui faire interpréter un scénario avec un ensemble de commandes qui lui permettent de se synchroniser avec le processus fils. En d'autres termes, on pourrait remplacer l'opérateur humain par un script de commandes. C'est tout simplement ce qui est fait par le programme pdip [3]que nous voyons dans la suite.

5. Présentation de PDIP

pdip est l'acronyme de « Programmed Dialogue with Interactive Programs ». En français, cela donne « Dialogue Programmé avec des Programmes Interactifs ». Ce nom provient des premières lignes du manuel du programme expect [4] dont pdip se veut être une version extrêmement simplifiée. C'est un utilitaire qui accepte un scénario en entrée pour dialoguer avec un programme interactif. pdip est disponible en source libre sur Sourceforge.

Comme expect, pdip accepte un langage de commandes pour envoyer et recevoir des chaînes de caractères à un programme. Mais, contrairement à expect, le langage de commandes n'est pas évolué au point d'accepter des structures de contrôle de haut niveau ou des branchements. Il ne permet pas non plus de dialoguer avec plusieurs programmes en même temps ou de rendre la main à l'opérateur en cours de session.

Comme indiqué en figure 5, pdip reçoit en paramètre le chemin d'accès du programme interactif à exécuter et, pour interagir avec, il accepte un langage de commandes simples sur son entrée standard ou sous forme d'un script passé en paramètre.

Figure 5 : Vue générale du fonctionnement de PDIP

La liste des commandes disponibles pouvant être interprétées par PDIP est :

- #

Début de commentaire.

- timeout x

Positionne à x secondes le temps maximum à attendre après chacune des commandes qui suivent (la valeur 0 annule le temporisateur. C’est la comportement par défaut).

- recv "w1 w2..."

Attend une chaîne de caractères venant du programme se conformant au modèle w1 w2... Le modèle est une expression régulière conforme à regex [2].

- send "w1 w2..."

Envoie la chaîne de caractères w1 w2... au programme. La chaîne peut contenir les caractères de contrôle suivants :

\a Cloche
\b Retour arrière
\t Tabulation horizontale
\n Retour à la ligne
\v Tabulation verticale
\f Saut de page
\r Retour chariot
\" Guillemet
\\ Barre oblique inversée

- sleep x

Arrête toute activité pendant x secondes.

- exit

Fin de session.

6. Utilisation de PDIP

Utiliser pdipest d'une simplicité extrême comme on le voit ici avec le pilotage d'un client telnet qui se connecte à une machine appelée remote avec le nom de login foo et le mot de passe bar. Ensuite la commande ls est exécutée avant de terminer la session :

$ pdip --cmd=’telnet remote’

recv "login"   # Attente du prompt « login: »

send "foo\n"   # Envoi du nom de login ’foo’

recv "Password"# Attente du prompt « Password »

send "bar\n"   # Envoi du mot de passe ’bar’

recv "\$ "     # Attente du prompt du shell « $ »

send "ls\n"    # Lancement de la commande ’ls’

recv "\$ "     # Attente du prompt du shell « $ »

send "exit\n" # Sortie du shell

exit           # Sortie de PDIP

$

Le script est largement commenté. On précisera toutefois qu’étant donné que la commande recv reçoit une expression régulière en paramètre, il convient de ne pas oublier d'inhiber les caractères spéciaux tels que $ à l'aide du caractère \. D'où la commande recv "\$ " pour attendre le prompt du shell. Voici un exemple de résultat de ce script pdip :

$ pdip --cmd='telnet remote'

recv "login »

Trying 192.0.1.12...

Connected to remote.

Escape character is '^]'.

Linux 2.6.22-14-generic (remote) (pts/10)

remote loginsend "foo\n"

recv "Password"

: foo

Passwordsend "bar\n"

recv "\$ "

:

Last login: Tue Nov 6 20:06:51 CET 2007 on :0

Linux remote 2.6.22-14-generic #1 SMP Sun Oct 14 23:05:12 GMT 2007 i686

The programs included with the Ubuntu system are free software;

the exact distribution terms for each program are described in the

individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by

applicable law.

foo@remote:~$ send "ls\n"

recv "\$ "

ls

DIR2              DOCUMENTS    PERSO       TODO

Applications      PHOTOS       VIDEOS

foo@remote:~$ send "exit\n"

exit

Conclusion

La notion de pseudo-terminal est très largement utilisée dans le monde Unix en général à travers les utilitaires les plus célèbres tels que telnet ou xterm. Cet article a présenté l'une de ses applications à travers pdip qui a pour but de piloter des programmes interactifs.

Notes

[1] man 7 pty

[2] man 7 regex

Liens

[3] Site de PDIP : http://pdip.sourceforge.net

[4] Site d’EXPECT : http://expect.nist.gov