problem beim aufruf von externen programmen
-
hi,
ich verwende unter fc6/eclipse/cdt eine process klasse um von meinem projekt aus andere programme zu starten:
string CProcess::executeProcess (string cmd, bool stopOnReturnErrorCode) /* * This method uses popen and pclose to receive a stream of text from a system process. * params: * cmd: command to execute * stopOnReturnErrorCode: if your command has a return code which is not 0, the LOGGER is called with param ERROR * return: the messages recived from the started process */ { char psBuffer[128]; FILE *pPipe; string returnMsg; string logMsg; int returnCode; if((pPipe = popen(cmd.c_str(), "r")) == NULL) { char errorBuff [256]; sprintf(errorBuff, "could not execute command '%s'.\n", cmd.c_str()); LOGGER.log("CProcess::executeProcess", errorBuff, ERROR); } else { /* Read pipe until end of file. */ while(!feof(pPipe)) { if(fgets(psBuffer, 128, pPipe) != NULL) // printf(psBuffer); returnMsg = returnMsg + psBuffer; } /* Close pipe and print return value of pPipe. */ returnCode = pclose(pPipe); if (returnMsg[returnMsg.length()-1] == '\n') { // avoid the linebreak in the logger returnMsg[returnMsg.length()-1] = ' '; } stringstream ss; ss << returnCode; string returnCodeString (ss.str()); logMsg = "the command \"" + cmd + "\" returns the message :\"" + returnMsg + "\" and returnCode is: " + returnCodeString + ""; LOGGER.log("CProcess::executeProcess", logMsg + "", DEBUG2); if (stopOnReturnErrorCode && returnCode!=0) { LOGGER.log("CProcess::executeProcess", "your last command returned an error code", ERROR); } } return returnMsg; }
das funktioniert auch, das problem liegt nur darin, dass ich nicht jeden output des aufgerufenen programs fangen kann. ein teil landet zwar auf der konsole, nicht jedoch in der pipe die die messages vom process holen soll. das sieht dann in etwa so aus:
consolenoutput:
DEBUG1: CEnergyMinimisation::fillBox (15.3.2007-21:4:44): just entered ------------------------------------------------------- Program genbox, VERSION 3.3.1 Source code file: futil.c, line: 266 Fatal error: Won't make more than 128 backups of 1mi5BindingRegionsOnly_tmp_Mut.top for you ------------------------------------------------------- DEBUG2: CProcess::executeProcess (15.3.2007-21:4:58): the command "genbox ....." returns the message :"WARNING: masses will be determined based on residue and atom names, this can deviate from the real mass of the atom type" and returnCode is: 65280
logfile content:
DEBUG1: CEnergyMinimisation::fillBox (15.3.2007-21:4:44): just entered DEBUG2: CProcess::executeProcess (15.3.2007-21:4:58): the command "genbox ....." returns the message :"WARNING: masses will be determined based on residue and atom names, this can deviate from the real mass of the atom type" and returnCode is: 65280
ich könnte zwar eine std error umleitung dirket an den auszuführenden command anhängen, aber das wäre (1) keine schoene loesung und (2) muesste ich den log dann erst wieder einlesen um ihn durchparsen zu koennen
wie bekomme ich also ueber meine pipe alles was der process von sich gibt herein?
danke
lordy
-
Du kannst mittels man: pipe(2) und man: execl(3) kannst du dir quasi popen nachbauen und stderr und stdout abfragen.
-
rüdiger schrieb:
Du kannst mittels man: pipe(2) und man: execl(3) kannst du dir quasi popen nachbauen und stderr und stdout abfragen.
ich hätte das mittels eines beispiels [1] versucht, ich weiss aber leider nach wie vor nicht wie ich an den stderr rankomme ... kannst du mir das codemässig vll ein bisschen anskizzieren?
danke
lordy[1] http://www.c-plusplus.net/forum/viewtopic-var-t-is-18435-and-view-is-previous.html
-
In dem Beispiel fehlt IMHO noch das Ersetzen des Standard-Output-Kanals des Kindprozesses durch die Schreibseite der Pipe. Evtl. wird dann klarer was passiert:
case 0 : /* Sohn Prozess */ { dup2(fd[PIPE_OUT], STDOUT_FILENO); /* Schreibseite der Pipe auf stdout biegen */ close(fd[PIPE_IN]); /* Leseseite der Pipe schliessen */ execl("/bin/ls", "ls", "-1" , path, NULL); /* externes Programm aufrufen */ /* Wenn execl erfolgreich war, kommt der Prozess nie hier an. Wenn nicht, schließt exit sowieso alle FDs. Ergo ist der folgende Befehl unnötig. */ /* close(fd[PIPE_OUT]); Schreibseite der Pipe schliessen, */ /* um Ende der Uebertragung anzuzeigen */ exit(0); }
Um nun stderr auch abzufangen, musst Du dup2 nur nochmal für STDERR_FILENO wiederholen. Wenn Du stdout und stderr getrennt lesen können möchtest (was meist sinnvoll ist da die Meldungsausgabe auf stderr die Datenausgabeauf stdout stören könnte), musst Du zwei pipe()s auf unterschiedliche int-Arrays ausführen und den dup2 für STDERR_FILENO dann auf die Ausgabeseite der zweiten Pipe legen.
EDIT:
Eine popen2-Funktion beschränkt auf die Ausgabekanäle wäre demnach z.B. wie folgt zu realisieren:pid_t popen2(FILE* files[], char const* command) { static const int PIPE_READ = 0; static const int PIPE_WRITE = 1; int outfd[2], errfd[2]; pid_t pid; /* HINT: Falls der zweite Befehl fehlschlägt werden die outfd nicht abgeräumt */ if (pipe(outfd) == -1 || pipe(errfd) == -1) return -1; pid = fork(); if (pid == -1) return -1; if (pid == 0) { /* Kindprozess */ /* HINT: Ab hier werden dem Aufrufer keine Fehler mehr mitgeteilt */ dup2(outfd[PIPE_WRITE], STDOUT_FILENO); /* stdout umleiten */ dup2(errfd[PIPE_WRITE], STDERR_FILENO); /* stderr umleiten */ close(outfd[PIPE_READ]); /* Leseseiten schließen */ close(errfd[PIPE_READ]); execl("/bin/sh", "sh", "-c", command, NULL); /* Programm wie popen() oder system() durch Shell-Aufruf starten */ exit(0); /* Ende falls execl fehlgeschlagen ist */ } close(outfd[PIPE_WRITE]); /* Schreibseiten schließen */ close(errfd[PIPE_WRITE]); /* HINT: Rückgabe von fdopen wird nicht auf Fehler geprüft */ files[0] = fdopen(outfd[PIPE_READ], "r"); /* Leseseiten an Aufrufer zurückgeben */ files[1] = fdopen(errfd[PIPE_READ], "r"); return pid; } void pclose2(pid_t pid, FILE* files[]) { int status; /* HINT: Auch hier fehlt die Fehlerbehandlung völlig */ waitpid(pid, &status, 0); /* Warten auf Programmende */ fclose(files[0]); fclose(files[1]); } int main() { FILE* pipes[2]; pid_t pid = popen2(pipes, "/bin/ls -lR /"); if (pid == -1) return 1; while (!feof(pipes[0]) || !feof(pipes[1])) { } pclose2(pid, pipes); }
HINT: Das ist frei von der Seele weg getippt und ungetestet
-
dank dir vielmals! haut super hin!
-
ein problem hab ich noch:
falls der prozess einen fehlercode liefert, hat in der alten implementierung pclose diesen zurückgeliefert.ich hätte es jetzt so versucht
int CProcess::pclose2(pid_t pid, FILE* files[]) { int status; waitpid(pid, &status, 0); /* wait until program ends */ int closeOutCode = fclose(files[0]); int closeErrCode = fclose(files[1]); if (closeOutCode != 0) { LOGGER.log("CProcess::pclose2", "fclose(files[0]) did not return 0", WARNING); return closeOutCode; } if (closeErrCode != 0) { LOGGER.log("CProcess::pclose2", "fclose(files[1]) did not return 0", WARNING); return closeErrCode; } return 0; }
was aber leider immer 0 liefert, auch dann wenn ich zb dem prozess absichtlich ein kill schicke
-
Wenn du den Fehlercode auslesen willst, dann solltest du dir die Variable status angucken (die man: waitpid übergeben wird).