probleme avec un "open pipe" sous windows

Le
gerbier
bonjour

contexte :
os : windows xp
perl : activeperl 5.8.2

Depuis un programme perl, je veux exécuter un autre programme du même
répertoire, et récupérer ses sorties. Pour cela, j'utilise la commande
open avec un "fichier" sous la forme "programme options |". Voici mon
code de test
####################################################

#!/usr/bin/perl

use strict;
use warnings;
use File::Basename;

my $path=dirname($0);
$path =~ s!\!/!g;
$path .= '/';
print "path=$path";

####################################################
sub test_open1($$) {
my $cmd = shift @_;
my $option = shift @_;

if (open (my $fh, "$cmd $option |") ) {
while (<$fh>) {
print "test_open1 : $_";
}
close $fh;
} else {
warn "impossible d'ouvrir $cmd ! $!";
}
}
####################################################

test_open1('essai2.pl', '');
test_open1('essai2.pl', 'sans path');
test_open1($path . 'essai2.pl', '');
test_open1($path . 'essai2.pl', 'avec path');

#########################################################

et voila la sortie :

C:Program Filestmp>essai.pl
path=C:/Program Files/tmp/
test_open1 : essai2 : C:Program Filestmpessai2.pl

test_open1 : essai2 : C:Program Filestmpessai2.pl sans path

test_open1 : essai2 : C:Program Filestmpessai2.pl

'C:/Program' n'est pas reconnu en tant que commande interne
ou externe, un programme exécutable ou un fichier de commandes.

C:Program Filestmp>

######################################################

les trois premiers appels fonctionnent, mais pas le 4eme (path + option)
, ce qui est justement ce que cherche à obtenir. J'ai ce problème lors
d'un portage de code d'unix vers windows.

Quelqu'un peut-t'il expliquer pourquoi ça plante, et surtout proposer
une solution qui marche !
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
jl_morel
Le #141962
Dans l'article a dit...

contexte :
os : windows xp
perl : activeperl 5.8.2

Depuis un programme perl, je veux exécuter un autre programme du même
répertoire, et récupérer ses sorties. Pour cela, j'utilise la commande
open avec un "fichier" sous la forme "programme options |". Voici mon
[couic]

'C:/Program' n'est pas reconnu en tant que commande interne
ou externe, un programme exécutable ou un fichier de commandes.

Quelqu'un peut-t'il expliquer pourquoi ça plante, et surtout proposer
une solution qui marche !



Le problème provient de l'espace dans votre nom de chemin.
Si dans une console vous tapez
C:/Program Files/tmp/essai2.pl arg1 arg2

vous aurez le message d'erreur 'C:/Program' n'est pas reconnu en tant que
commande interne ou externe...etc car l'espace sert aussi à séparer les
arguments. La ligne est interprétée avec la commande C:/Program et les
trois arguments : Files/tmp/essai2.pl, arg1, arg2

Pour lever l'ambiguité il faut utiliser des guillemets :
"C:/Program Files/tmp/essai2.pl" arg1 arg2

Dans votre script, il suffit de rajouter des guillemets autour de $cmd

if (open (my $fh, ""$cmd" $option |") ) {

HTH

--
J-L.M.
http://www.bribes.org/perl

gerbier
Le #141961
Jean-Louis MOREL wrote:
Dans l'article a dit...
contexte :
os : windows xp
perl : activeperl 5.8.2

Depuis un programme perl, je veux exécuter un autre programme du même
répertoire, et récupérer ses sorties. Pour cela, j'utilise la commande
open avec un "fichier" sous la forme "programme options |". Voici mon
[couic]

'C:/Program' n'est pas reconnu en tant que commande interne
ou externe, un programme exécutable ou un fichier de commandes.

Quelqu'un peut-t'il expliquer pourquoi ça plante, et surtout proposer
une solution qui marche !



Le problème provient de l'espace dans votre nom de chemin.


c'est bien ce que soupçonnais

Si dans une console vous tapez
C:/Program Files/tmp/essai2.pl arg1 arg2

vous aurez le message d'erreur 'C:/Program' n'est pas reconnu en tant que
commande interne ou externe...etc car l'espace sert aussi à séparer les
arguments. La ligne est interprétée avec la commande C:/Program et les
trois arguments : Files/tmp/essai2.pl, arg1, arg2


ce que je ne comprends pas, c'est pourquoi sans arguments, il
n'interprete pas "C:/Program" comme une commande et
"Files/tmp/essai2.pl" comme un argument ? une bizarreté de
l'interpreteur de commande ?

Pour lever l'ambiguité il faut utiliser des guillemets :
"C:/Program Files/tmp/essai2.pl" arg1 arg2

Dans votre script, il suffit de rajouter des guillemets autour de $cmd

if (open (my $fh, ""$cmd" $option |") ) {


merci, ça marche !

(j'en étais presque à recoder tout ça avec IPC::Open3 )


Paul Gaborit
Le #141849
À (at) Mon, 26 Mar 2007 16:30:01 +0200,
gerbier
ce que je ne comprends pas, c'est pourquoi sans arguments, il
n'interprete pas "C:/Program" comme une commande et
"Files/tmp/essai2.pl" comme un argument ? une bizarreté de
l'interpreteur de commande ?


Tout simplement parce que s'il n'y a pas d'argument, perl ne passe pas
l'interpréteur de commandes (sur Windows) ou par le shell (sur
Unix). Ça évite tous les risques liés à ce passage. Vous êtes tombés
sur l'un des problèmes. Sachez que vous avez potentiellement le même
problème sur tous les arguments. Et là, ce ne sera peut-être pas
visible lors de l'exécution... Un solution pour être sûr et si vous
utilisez un perl récent, consiste à utiliser la syntaxe étendue de
open :

open mt $fh, "-|", "C:/Program Files/.../", $arg1, $arg2
or die "Can't lauch '...': $!n';

[...]
merci, ça marche !

(j'en étais presque à recoder tout ça avec IPC::Open3 )


Ce serait une autre bonne solution (lire perlipc pour en savoir plus)
pour garantir le comportement.

--
Paul Gaborit - Perl en français -
gerbier
Le #141848
Paul Gaborit wrote:
À (at) Mon, 26 Mar 2007 16:30:01 +0200,
gerbier
ce que je ne comprends pas, c'est pourquoi sans arguments, il
n'interprete pas "C:/Program" comme une commande et
"Files/tmp/essai2.pl" comme un argument ? une bizarreté de
l'interpreteur de commande ?


Tout simplement parce que s'il n'y a pas d'argument, perl ne passe pas
l'interpréteur de commandes (sur Windows) ou par le shell (sur
Unix). Ça évite tous les risques liés à ce passage. Vous êtes tombés
sur l'un des problèmes. Sachez que vous avez potentiellement le même
problème sur tous les arguments. Et là, ce ne sera peut-être pas
visible lors de l'exécution... Un solution pour être sûr et si vous
utilisez un perl récent, consiste à utiliser la syntaxe étendue de
open :

open mt $fh, "-|", "C:/Program Files/.../", $arg1, $arg2
or die "Can't lauch '...': $!n';


oui, j'ai vu ça sur un perl 5.8, et ça me plait bien,
mais je dois conserver une compatibilité avec du perl 5.6


(j'en étais presque à recoder tout ça avec IPC::Open3 )


Ce serait une autre bonne solution (lire perlipc pour en savoir plus)
pour garantir le comportement.


j'ai regardé perlfaq8
(ex :
http://perl.enstimac.fr/DocFr/perlfaq8.html#comment%20capturer%20la%20sortie%20stderr%20d'une%20commande%20externe)
et j'ai deux questions sur un code comme ci-dessous :

my($wtr, $rdr, $err);
$pid = open3($wtr, $rdr, $err, 'cmd', 'optarg', ...);
while(my $line = <$rdr>) {
...
}
waitpid($pid, 0);

- je ne vois pas de close : est-ce normal ?
- est-ce qu'il y a un inconvénient à ne lire qu'un seul des flux (ici
$rdr) ?


merci beaucoup pour votre reponse (nette et précise, comme d'habitude)


Paul Gaborit
Le #141846
À (at) Mon, 26 Mar 2007 18:03:28 +0200,
gerbier
j'ai regardé perlfaq8
(ex :
http://perl.enstimac.fr/DocFr/perlfaq8.html#comment%20capturer%20la%20sortie%20stderr%20d'une%20commande%20externe)
et j'ai deux questions sur un code comme ci-dessous :

my($wtr, $rdr, $err);
$pid = open3($wtr, $rdr, $err, 'cmd', 'optarg', ...);
while(my $line = <$rdr>) {
...
}
waitpid($pid, 0);

- je ne vois pas de close : est-ce normal ?


Oui et non... Le 'close' est implicite en fin de script. Mais on peut
aussi le gérer soi-même. Ceci étant les 'close' ne peuvent être fait
qu'après le 'waitpid' puisque tant que le processus fils est vivant il
peut encore écrire et même lorsqu'il est terminé, il peut encore
rester des choses à lire...

- est-ce qu'il y a un inconvénient à ne lire qu'un seul des flux (ici
$rdr) ?


Oui. C'est même la plus grosse difficulté : tel quel, il y a risque
d'interblocage (de deadlock). On attend bêtement quelque chose sur la
sortie standard du processus fils alors qu'il est lui-même bloqué en
écriture sur la sortie d'erreur (car on ne la lit pas). Il faut donc
soit passer par des fichiers temporaires (ou /dev/null) ou alors faire
des lectures/écritures non-bloquantes.

--
Paul Gaborit - Perl en français -
Nicolas George
Le #141845
Paul Gaborit wrote in message
Oui et non... Le 'close' est implicite en fin de script. Mais on peut
aussi le gérer soi-même. Ceci étant les 'close' ne peuvent être fait
qu'après le 'waitpid' puisque tant que le processus fils est vivant il
peut encore écrire et même lorsqu'il est terminé, il peut encore
rester des choses à lire...


Tu pipotes très largement.

D'une part, ici, le close comme le wait sont faits après une boucle
attendant la fin de fichier sur le pipe. Quand la fin de fichier est reçue,
il est certain que le processus à l'autre bout ne peut plus écrire, donc le
close est sûr.

D'autre part, tant que le fichier n'a pas été fermé, le processus à l'autre
bout peut être bloqué en écriture : faire le wait avant le close provoquera
donc un deadlock. Au contraire, le close avant le wait tuera le processus à
l'autre bout avec un SIGPIPE, ce qui est préférable.

C'est encore plus vrai si le canal est bidirectionnel : même s'il a fermé en
écriture, le processus à l'autre bout peut attendre une fin de fichier en
lecture avant de se terminer.

Pour toutes ces raisons, il faut toujours fermer les pipes avant d'attendre
le processus fils.

Paul Gaborit
Le #141844
À (at) 27 Mar 2007 12:26:39 GMT,
Nicolas George
Tu pipotes très largement.


Dire je débloque, ça oui ! Mais pipoter, je ne crois pas. En fait, je
débloque... les écritures et les lectures. ;-)

D'une part, ici, le close comme le wait sont faits après une boucle
attendant la fin de fichier sur le pipe. Quand la fin de fichier est reçue,
il est certain que le processus à l'autre bout ne peut plus écrire, donc le
close est sûr.


Sauf qu'ici, il y a trois canaux (entrée standard, sortie standard et
sortie d'erreur). N'en gérez qu'un seul en lecture bloquante n'a pas
de sens qu'on fasse ou non le 'close' après. La fin de la sortie
standard n'indique pas nécessairement la fin des entrées ou des
sorties d'erreur du processus ni sa terminaison...

D'autre part, tant que le fichier n'a pas été fermé, le processus à l'autre
bout peut être bloqué en écriture : faire le wait avant le close provoquera
donc un deadlock. Au contraire, le close avant le wait tuera le processus à
l'autre bout avec un SIGPIPE, ce qui est préférable.


Tout cela est vrai si on considère des opérations bloquantes. Mais à
partir du moment où il y a plusieurs canaux, sauf à maîtriser
parfaitement le fonctionnement des deux processus communicants, on ne
peut plus se reposer sur des opérations bloquantes.

Or, de manière standard, il n'existe pas de moyens de faire
simultanément des wait et des lectures/écritures non bloquants (ce ne
sont pas les mêmes appels systèmes). On peut donc se retrouver avec un
wait qui déclenche alors qu'on n'a pas fini de lire tout ce que le
processus a écrit ou avec un canal qui se ferme sans que le processus
se termine (il continue à écrire sur un autre canal...), etc. Oublier
cela peut amener à des bugs : c'est ce que j'ai corrigé dans
Parallel::Jobs. Bon, c'est peut-être un contexte particulier
(puisqu'il y a de nombreux fils à gérer).

C'est encore plus vrai si le canal est bidirectionnel : même s'il a fermé en
écriture, le processus à l'autre bout peut attendre une fin de fichier en
lecture avant de se terminer.


Pour les canaux qu'on gère en écriture, je suis d'accord : le 'close'
explicite est important pour signaler la fin au processus qui est à
l'autre bout.

Pour les canaux en lecture, s'il n'y a plus rien à lire c'est que le
canal a été fermé à l'autre bout. Le 'close' explicite est donc
nettement moins important.

Pour toutes ces raisons, il faut toujours fermer les pipes avant d'attendre
le processus fils.


Si on se place dans un contexte non-bloquant, ce n'est pas toujours
nécessaire ni possible.

--
Paul Gaborit - Perl en français -
Nicolas George
Le #141843
Paul Gaborit wrote in message
Sauf qu'ici, il y a trois canaux (entrée standard, sortie standard et
sortie d'erreur).


Ça ne change rien à ce que j'ai dit : si on a reçu une fin de fichier, on
peut fermer, toujours.

Tout cela est vrai si on considère des opérations bloquantes. Mais à
partir du moment où il y a plusieurs canaux, sauf à maîtriser
parfaitement le fonctionnement des deux processus communicants, on ne
peut plus se reposer sur des opérations bloquantes.


Il suffit que l'un des deux processus soit asynchrone, il n'est pas
nécessaire que les deux le soient. Le plus souvent, le père sera asynchrone
et gérera les bizarreries, tandis que le fils est un simple filtre écrit
dans un cadre plus général.

Or, de manière standard, il n'existe pas de moyens de faire
simultanément des wait et des lectures/écritures non bloquants


Euh, si, ça s'appelle SIGCHLD.

Pour les canaux en lecture, s'il n'y a plus rien à lire c'est que le
canal a été fermé à l'autre bout. Le 'close' explicite est donc
nettement moins important.


Dans ce cas, on ne fait pas le wait non plus. Si on fait le wait, on doit
faire les close, et on doit les faire avant.

Si on se place dans un contexte non-bloquant, ce n'est pas toujours
nécessaire ni possible.


Je maintiens que c'est toujours possible.

La seule exception qu'on peut y faire, c'est quand le processus fils va
lui-même avoir des descendants qui vont lui survivre, et garder les pipes
ouverts. Dans tous les autres cas, c'est un bug de faire un wait avant
d'avoir fermé tous les canaux.

Paul Gaborit
Le #141725
À (at) 27 Mar 2007 15:08:09 GMT,
Nicolas George
Ça ne change rien à ce que j'ai dit : si on a reçu une fin de fichier, on
peut fermer, toujours.


Certes... Mais si on détecte la fin de fichier, le close ne sert qu'à
libérer de la ressource système (le processus à l'autre bout à
lui-même fermer le canal et on a déjà tout lu).

Il suffit que l'un des deux processus soit asynchrone, il n'est pas
nécessaire que les deux le soient. Le plus souvent, le père sera asynchrone
et gérera les bizarreries, tandis que le fils est un simple filtre écrit
dans un cadre plus général.


Ok.

Or, de manière standard, il n'existe pas de moyens de faire
simultanément des wait et des lectures/écritures non bloquants


Euh, si, ça s'appelle SIGCHLD.


C'est un moyen détourné utilisant les signaux (qui n'étaient pas
simples à gérer en Perl). Il n'y pas d'appel système (en tous cas pas
dans tous les systèmes) qui mixent les fonctionnalités de 'select' et
de 'wait'.

Pour les canaux en lecture, s'il n'y a plus rien à lire c'est que le
canal a été fermé à l'autre bout. Le 'close' explicite est donc
nettement moins important.


Dans ce cas, on ne fait pas le wait non plus. Si on fait le wait, on doit
faire les close, et on doit les faire avant.


C'est le 'on doit le faire avant' que je trouve abusif ici. Le fils
peut très bien fermer ses canaux et quitter (ce qui les ferment aussi
de fait). Dans ce cas, il n'y aucun problème à faire le 'wait' sur ce
fils pour ensuite terminer les lectures et appeler close de son
côté. Je ne crois pas d'ailleurs que Unix (ou autres normes)
garantisse l'ordre dans lequel les événements arrivent au père entre
le SIGCHLD ou le déclenchement du 'select' en supposant que le père
soit bloqué sur un 'select' en attente du fils... En tous cas (sur
Solaris, c'est sûr et si mes souvenirs sont bons sur FreeBSD aussi),
j'ai déjà constaté un ordre d'arrivée inversé : on reçoit le signal de
la mort du fils alors qu'on n'a pas encore reçu tout ce qu'il nous a
envoyé.

Si on se place dans un contexte non-bloquant, ce n'est pas toujours
nécessaire ni possible.


Je maintiens que c'est toujours possible.


Effectivement, j'ai été un peu vite en besogne : c'est toujours
possible mais ce n'est pas indispensable et pour le faire
correctement, il ne faut pas se baser sur l'ordre de réception des
événements.

Pour en revenir à la demande initiale, l'appel à 'close' sur une canal
déjà fermé (puisqu'il n'y a plus rien à y lire) n'a rien
d'indispensable. Ce n'est donc pas l'absence de 'close' qui est
choquante. Ce qui est choquant, c'est l'usage d'une lecture bloquante
de la sortie standard du fils alors qu'on a trois canaux ouverts avec
ce fils et qu'on se met donc potentiellement en position
d'interblocage.

La seule exception qu'on peut y faire, c'est quand le processus fils va
lui-même avoir des descendants qui vont lui survivre, et garder les pipes
ouverts. Dans tous les autres cas, c'est un bug de faire un wait avant
d'avoir fermé tous les canaux.


C'est encore une autre situation qu'on va laisser de côté...

--
Paul Gaborit - Perl en français -

Publicité
Poster une réponse
Anonyme