[Perl] Question sur qx

Olivier Mengué olivier.mengue at gmail.com
Jeu 5 Nov 10:39:41 CET 2020


Bonjour Philippe,

L'opérateur qx capture ce qui est envoyé par la commande sur la sortie
standard.

Voyons donc ce qui est envoyé sur la sortie standard. Pour cela il suffit
de comparer les deux commandes suivantes depuis un shell :
    read -n 1 -p "un message " l
    read -n 1 -p "un message " l >/dev/null
La première commande laisse passer ce qui est envoyé sur la sortie standard
pour l'envoyer vers le terminal, tandis que la seconde l'envoie à la
poubelle.
Résultat : les affichages sur le terminal sont identiques. Pourquoi ? Parce
que la commande n'écrit rien sur la sortie standard. Le caractère tapé (qui
est affiché) n'est d'ailleurs pas envoyé par la commande. C'est juste le
comportement normal du terminal où les caractères tapés sont répliqués à
l'affichage (Notez qu'il existe un moyen de désactiver ce comportement, par
exemple pour la saisie de mot de passe).
Donc forcément, qx ne capture rien.

Je t'invite à relire le manuel de la commande "read". C'est d'ailleurs une
commande spéciale : c'est un "shell builtin", c'est à dire une commande
interne. C'est donc dans la documentation de ton shell que tu trouveras ces
informations : "man bash", puis chercher dans la section "SHELL BUILTIN
COMMANDS". En ligne et en français ici : https://man.cx/bash(1)/fr#heading31

Que fait cette commande "read" : elle lit des caractères sur l'entrée
standard (connectée par défaut au terminal) et stocke le résultat dans une
variable d'environnement, en l'occurence "l" puisque c'est le nom donné sur
la ligne de commande. Comme c'est une commande interne, la valeur lue reste
dans l'environnement privé du shell. Il faut trouver un moyen de la
communiquer à l'extérieur. Il suffit pour cela de demander l'affichage du
contenu de cette variable. Avec la commande "printf" (pour qu'il n'y ait
pas de \n ajouté comme avec "echo").

    { read -n 1 -p "un message " l ; printf %s $l; }
    { read -n 1 -p "un message " l ; printf %s $l; } > /dev/null

Cette fois il y a bien une différence entre les deux commandes : la
première affiche bien le résultat.
Note: les accolades autour des deux commandes permettent de les grouper
afin de gérer la redirection pour les deux commandes (read, printf)
ensembles.

Il est donc maintenant possible d'utiliser qx (notez qu'il faut échapper
les % et les $) :
    $c=qx{read -n 1 -p "un message " l;  printf \%s \$l};

Exemple :
    perl -E '$c=qx{read -n 1 -p "un message " l; printf \%s \$l}; say
"\nCaractère lu: [$c]"'

J'ai écrit tout ceci pour expliquer ce qui se passe. Mais ce n'est
absolument pas la bonne façon d'écrire du Perl. Voici pourquoi. La raison
principale est que le script Perl lance un shell qui va exécuter deux
commandes.
- ces deux commandes sont des commandes internes du shell. Donc le
programme perl dépend du shell utilisé dans l'environnement où le script
est exécuté. Si le shell de l'utilisateur est fish au lieu de bash, ou si
le script est exécuté sous Windows, le script ne marchera plus.
- le fait de lancer un shell (c.à.d. un nouveau processus) pour faire
l'opération est un gaspillage des ressources de l'ordinateur.

If faut donc plutôt chercher une solution en pur Perl qui sera à la fois
plus portable et plus efficace.
La fonction sysread (perldoc -f sysread) permet directement de lire des
octets.
Essayons :
    perl -E 'print "un message "; sysread(STDIN, $c, 1); say "\nCaractère
lu: [$c]"'
Est-ce que ça marche ? Non. Deux problèmes :
1. le message "un message " n'est pas affiché avant la saisie. Problème
avec STDOUT.
2. le programme lit bien un caractère, mais il est bloqué tant que l'on ne
tape pas Entrée. Problème avec STIN.

Ces deux problèmes sont liés aux mécanismes d'échange des programmes avec
le terminal : le buffering. C'est un mécanisme qui date d'une époque où
l'on voulait réduire optimiser les communications avec le terminal et pour
cela réduire les échanges en envoyant et recevant des lignes complètes
plutôt que caractère par caractère. Donc en sortie le contenu n'est envoyé
au terminal que lorsqu'on envoye un saut de ligne, et en entrée le terminal
n'envoie ce qui a été tapé que lorsque la touche Entrée est appuyée. Nous
avons donc besoin de désactiver ces deux mécanismes pour que notre
programme fonctionne correctement.
Pour l'entrée, il faut activer l"autoflush" (voir perldoc IO::Handle).
https://metacpan.org/pod/IO::Handle
Pour la sortie, on peut utiliser Term::ReadKey qui permet de contrôler le
terminal. https://metacpan.org/pod/Term::ReadKey
   ReadMode 'cbreak'; # pour désactiver le buffering
   ReadMode 'normal'; # pour le réactiver


    perl -MTerm::ReadKey -E 'STDOUT->autoflush(1); print "un message ";
ReadMode 'cbreak'; sysread(STDIN, $c, 1); ReadMode 'normal'; say
"\nCaractère lu: [$c]"'

Mais tout ceci est géré par le module IO::Prompt, alors autant l'utiliser.
https://metacpan.org/pod/IO::Prompt

Olivier.

Le jeu. 5 nov. 2020 à 07:19, Philippe Delavalade <
philippe.delavalade at orange.fr> a écrit :

> Bonjour la liste.
>
> Avec bash, on peut récupérer un caractère frappé au clavier sans qu'il soit
> suivi de <entrée>.
>
> Avec 'read -n 1 l'., le caractère frappé est dans la variable 'l'.
>
> Dans un script perl j'essaie de faire la même chose avec la ligne
> $c=qx{read -n 1 -p "un message " l}
> mais $c ne contient pas le caractère frappé au clavier. l'aide de perldoc
> sur qx ne m'a pas permis d'avancer.
>
> Je passe sûrement à côté d'un truc simple mais...
>
> Merci par avance de votre aide.
>
> --
> Philippe
> _______________________________________________
> Perl mailing list
> Perl at mongueurs.net
> http://listes.mongueurs.net/mailman/listinfo/perl
> Attention, les archives sont publiques
>
-------------- section suivante --------------
Une pièce jointe HTML a été nettoyée...
URL: <http://listes.mongueurs.net/archives/perl/attachments/20201105/d330477c/attachment.html>


Plus d'informations sur la liste de diffusion Perl