pourquoi ne puis-je pas rediriger la sortie des deux appels execl ?
Question
Doublons possibles :
- Comment appeler execl() en C avec les arguments appropriés ?
- Récupérer la sortie de l'exécutif
- Pipes Linux comme entrée et sortie
- Utiliser dup2 pour la tuyauterie
- Tuyauterie pour entrée/sortie
J'ai essayé d'apprendre le piping sous Linux en utilisant dup/dup2 et fork ces 3 derniers jours.Je pense que j'ai compris, mais lorsque j'appelle deux programmes différents à partir du processus enfant, il semble que je ne capture que la sortie du premier appelé. Je ne comprends pas pourquoi et/ou ce que je fais de mal.C'est ma question principale.
Modifier:Je pense qu'une solution possible consiste à créer un autre enfant et à configurer des tuyaux avec dup2, mais je me demande simplement pourquoi le code ci-dessous ne fonctionne pas.Ce que je veux dire, c'est que je m'attendrais à capturer stderr du premier appel execl et stdout du second.Cela ne semble pas se produire.
Ma deuxième question est de savoir si j'ouvre et ferme correctement les tuyaux.Sinon, j'aimerais savoir ce que je dois ajouter/supprimer/modifier.
Voici mon code :
#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <sys/wait.h>
#define READ_END 0
#define WRITE_END 1
void parentProc(int* stdoutpipe, int* stderrpipe);
void childProc(int* stdoutpipe, int* stderrpipe);
int main(){
pid_t pid;
int status;
int stdoutpipe[2]; // pipe
int stderrpipe[2]; // pipe
// create a pipe
if (pipe(stdoutpipe) || pipe(stderrpipe)){
std::cerr << "Pipe failed." << std::endl;
return EXIT_FAILURE;
}
// fork a child
pid = fork();
if (pid < 0) {
std::cerr << "Fork failed." << std::endl;
return EXIT_FAILURE;
}
// child process
else if (pid == 0){
childProc(stdoutpipe, stderrpipe);
}
// parent process
else {
std::cout<< "waitpid: " << waitpid(pid, &status, 0)
<<'\n'<<std::endl;
parentProc(stdoutpipe, stderrpipe);
}
return 0;
}
void childProc(int* stdoutpipe, int* stderrpipe){
dup2(stdoutpipe[WRITE_END], STDOUT_FILENO);
close(stdoutpipe[READ_END]);
dup2(stderrpipe[WRITE_END], STDERR_FILENO);
close(stderrpipe[READ_END]);
execl("/bin/bash", "/bin/bash", "foo", NULL);
execl("/bin/ls", "ls", "-1", (char *)0);
// execl("/home/me/outerr", "outerr", "-1", (char *)0);
//char * msg = "Hello from stdout";
//std::cout << msg;
//msg = "Hello from stderr!";
//std::cerr << msg << std::endl;
// close write end now?
}
void parentProc(int* stdoutpipe, int* stderrpipe){
close(stdoutpipe[WRITE_END]);
close(stderrpipe[WRITE_END]);
char buffer[256];
char buffer2[256];
read(stdoutpipe[READ_END], buffer, sizeof(buffer));
std::cout << "stdout: " << buffer << std::endl;
read(stderrpipe[READ_END], buffer2, sizeof(buffer));
std::cout << "stderr: " << buffer2 << std::endl;
// close read end now?
}
Lorsque je lance ceci, j'obtiens le résultat suivant :
yfp> g++ selectTest3.cpp; ./a.out
waitpid: 21423
stdout: hB�(6
stderr: foo: line 1: -bash:: command not found
Le code source du binaire "outerr" (commenté ci-dessus) est simplement :
#include <iostream>
int main(){
std::cout << "Hello from stdout" << std::endl;
std::cerr << "Hello from stderr!" << std::endl;
return 0;
}
Lorsque j'appelle "outerr", au lieu de ls ou "foo", j'obtiens le résultat suivant, auquel je m'attendrais :
yfp> g++ selectTest3.cpp; ./a.out
waitpid: 21439
stdout: Hello from stdout
stderr: Hello from stderr!
La solution
Sur execl
Une fois que vous avez appelé avec succès execl
ou toute autre fonction du exec
famille, le processus original est complètement écrasé par le nouveau processus.Cela implique que le nouveau processus ne « revient » jamais à l’ancien.Si tu en as deux execl
appels consécutifs, la seule façon d'exécuter le second est si le premier échoue.
Pour exécuter deux commandes différentes à la suite, vous devez fork
un enfant pour exécuter la première commande, wait
, fork
un deuxième enfant pour exécuter la deuxième commande, puis (facultatif) wait
pour le deuxième enfant aussi.
Sur read
Le read
L'appel système n'ajoute pas de valeur nulle de fin, donc en général, vous devez regarder la valeur de retour, qui vous indique le nombre d'octets réellement lus.Définissez ensuite le caractère suivant sur null pour obtenir une chaîne C, ou utilisez le constructeur de plage pour std::string
.
Sur les tuyaux
En ce moment, vous utilisez waitpid
attendre que le processus enfant soit déjà terminé, alors lecture des tuyaux.Le problème est que si le processus enfant produit beaucoup de résultats, alors il bloc parce que le tube est plein et que le processus parent ne le lit pas.Le résultat sera une impasse, car l'enfant attend que le parent lise et que le parent attend que l'enfant termine.
Ce que vous devriez faire, c'est utiliser select
attendre que les informations arrivent sur le côté de l'enfant stdout
ou celui de l'enfant stderr
.Lorsque l’entrée arrive, lisez-la ;cela permettra à l'enfant de continuer.Lorsque le processus enfant mourra, vous le saurez car vous obtiendrez la fin du fichier sur les deux. Alors vous pouvez appeler en toute sécurité wait
ou waitpid
.
Autres conseils
Le exec
famille de fonctions remplace l’image de processus actuelle par une nouvelle image de processus.Lorsque vous exécutez,
execl("/bin/bash", "/bin/bash", "foo", NULL);
le code du processus en cours n'est plus exécuté.C'est pourquoi vous ne voyez jamais le résultat de l'exécution
execl("/bin/ls", "ls", "-1", (char *)0);