Ausgabe von mit Pipe verbundenen Programm auffangen

Direkte Linux-Programmierung, POSIX
Antworten
nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von nufan » Mo Apr 05, 2010 5:00 am

Ich arbeite jetzt wortwörtlich den ganzen Tag an einem Problem und weiß einfach nicht mehr weiter...
Gleich mal vorweg: Ich weiß nicht, ob das was ich will überhaupt möglich ist ^^

Mein Programm ruft "sh" mittels popen () zum Schreiben auf, ich bekomme also eine Pipe. Das sieht in Code-Form so aus:

Code: Alles auswählen

FILE *shell = popen ("sh", "w");

if (shell == NULL)
{
  // Fehlerbehandlung
}

fprintf (shell, "irgendein shell befehl\n");
// ...
pclose (shell);
Wenn ich nun was mit fprintf () in die Pipe schreibe ( = eine Anwendung in "sh" ausführe) landet die Ausgabe in der selben Konsole wie jene von meinem Programm. Mein Programm gibt lediglich Debug-Ausgaben aus, also nichts für den Benutzer interessantes. Nun will ich aber nicht mein GUI-Programm mit Konsolenanwendungen mischen. Am schönsten wäre es die Ausgabe von "sh" irgendwie aufzufangen und im GUI anzeigen. Das Problem: Ich kann mit der Pipe entweder Daten schreiben oder lesen, aber nicht beides.
Ich hab bereits einiges mit der Ausgabe meiner Anwendung versucht (stdout mit setvbuf () puffern; stdout in Memorystream umleiten; ...), das scheint die externen Anwendungen aber recht wenig zu interessieren, die schreibt nämlich weiterhin auf die Konsole. Ich könnte natürlich die Ausgabe von "sh" mittels I/O-Redirection in eine Datei schreiben, das dann einzulesen wäre jedoch sehr umständlich und ein ziemlich großer Aufwand. Außerdem könnte der Benutzer auf die Idee kommen meine Log-Datei zu löschen... ;)
Systemunabhängigkeit ist schon Aufgrund des restlichen Programms komplett egal oder besser gesagt einfach unmöglich. Es reicht wenn es auf Ubuntu funktioniert - zumindest vorerst.

Hat jemand eine Idee wie ich das lösen könnte? Oder besser gefragt: Ist das überhaupt möglich?

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Xin » Mo Apr 05, 2010 8:45 am

nufan hat geschrieben:Ich arbeite jetzt wortwörtlich den ganzen Tag an einem Problem und weiß einfach nicht mehr weiter...
Gleich mal vorweg: Ich weiß nicht, ob das was ich will überhaupt möglich ist ^^
Ich denke, was Du willst, ist möglich, aber ich habe das Gefühl, dass Du nicht tust, was Du willst. ^^

und ich bin mir nicht 100%ig sicher, was Du willst.
nufan hat geschrieben:Mein Programm ruft "sh" mittels popen () zum Schreiben auf, ich bekomme also eine Pipe. Das sieht in Code-Form so aus:
Wenn ich nun was mit fprintf () in die Pipe schreibe ( = eine Anwendung in "sh" ausführe) landet die Ausgabe in der selben Konsole wie jene von meinem Programm. Mein Programm gibt lediglich Debug-Ausgaben aus, also nichts für den Benutzer interessantes.
Warum hast Du dann eine Pipe?

Benutze fork() und anschließend waitpid().
Vom Child-Task schließt Du den Output-Stream und ggfs. den Error-Stream und rufst system( "sh ... whatsoever" ) auf.

nufan hat geschrieben:Systemunabhängigkeit ist schon Aufgrund des restlichen Programms komplett egal oder besser gesagt einfach unmöglich. Es reicht wenn es auf Ubuntu funktioniert - zumindest vorerst.
Unter Ubuntu geht das auf jeden Fall.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Jside
Beiträge: 377
Registriert: Di Nov 11, 2008 12:56 am

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Jside » Mo Apr 05, 2010 9:31 am

Also mit dup2() kannste die ausgaben(bzw STDOUT/IN/ERR) von einem Programm in (eigene/andere-)Pipes/Dateien umleiten, musste probieren, ob das, da du die Shell per pipe aus dem Program heraus öffnest auch automatisch für eben diese gillt.

So a la:

Code: Alles auswählen

int err = open("/tmp/log", O_CREAT  | O_WRONLY | O_SYNC );
// fehlerhandhabung

dup2(STDOUT_FILENO, err);

fprintf(stdout, "Das wird jetzt auf /tmp/log geschrieben...\n");"
gings glaubisch, so rein aus dem Kopf erinnert..

nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von nufan » Mo Apr 05, 2010 11:12 am

Xin hat geschrieben:und ich bin mir nicht 100%ig sicher, was Du willst.
In meinem Programm brauche ich einige externe Tools (apt, mount, rsync, chmod...). Es wäre ziemlich aufwändig immer die entsprechenden Bibliotheken zu verwenden. Das würde ewig dauern mich darin einzuarbeiten. Deshalb setze ich diese Programme voraus und installiere sie falls sie nicht vorhanden sind.
Nun will ich diese Programme von meinem Programm aus ausführen und die Ausgabe im GUI anzeigen. D.h. ich muss irgendwie an die Ausgabe der in der Konsole ausgeführten Befehle kommen.
Xin hat geschrieben:Warum hast Du dann eine Pipe?

Benutze fork() und anschließend waitpid().
Vom Child-Task schließt Du den Output-Stream und ggfs. den Error-Stream und rufst system( "sh ... whatsoever" ) auf.
Am Anfang hab ichs auch mit system () versucht. Aber bei system () wird jedes Mal ein neuer Prozess angelegt, ich kann also z.B. nicht das Verzeichnis wechseln:

Code: Alles auswählen

#include <cstdlib>

int main ()
{

  system ("cd ordner");              // Verzeichnis wechseln
  system ("echo text > datei");   // landet im ursprünglichen Verzeichnis

  return 0;

}
Es ist wichtig, dass es die immer genau die selbe Shell ist, da eben auch das Verzeichnis gewechselt wird. Außerdem muss ich noch Umgebungsvariablen setzen, die nach dem system ()-Aufruf auch weg sind.
Jside hat geschrieben:

Code: Alles auswählen

int err = open("/tmp/log", O_CREAT  | O_WRONLY | O_SYNC );
// fehlerhandhabung

dup2(STDOUT_FILENO, err);

fprintf(stdout, "Das wird jetzt auf /tmp/log geschrieben...\n");"
Ich hab das jetzt mal so versucht:

Code: Alles auswählen

#include <cstdio>
#include <unistd.h>
#include <fcntl.h>

int main ()
{

  int err = open("log", O_CREAT  | O_WRONLY | O_SYNC );
  dup2 (STDOUT_FILENO, err);
  fprintf(stdout, "Das wird jetzt auf log geschrieben...\n");

  FILE *shell = popen ("sh", "w");
  fprintf (shell, "cd ..\n");
  fprintf (shell, "ls -la\n");
  pclose (shell);
  close (err);

  return 0;

}
Die erstellte Datei kann ich mit dem Texteditor nicht öffnen, laut "sudo od" (ja, ich brauche "sudo" um die Datei lesen zu können?!) steht folgendes drin:

Code: Alles auswählen

0000000
Die Ausgabe von "ls -la" landet trotzdem in der Konsole.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Xin » Mo Apr 05, 2010 11:21 am

nufan hat geschrieben:
Xin hat geschrieben:und ich bin mir nicht 100%ig sicher, was Du willst.
In meinem Programm brauche ich einige externe Tools (apt, mount, rsync, chmod...). Es wäre ziemlich aufwändig immer die entsprechenden Bibliotheken zu verwenden. Das würde ewig dauern mich darin einzuarbeiten. Deshalb setze ich diese Programme voraus und installiere sie falls sie nicht vorhanden sind.
Dann installiere Dich, setz Dich ins richtige Verzeichnis und starte Dich selbst neu, stelle damit fest, dass Du alles installiert hast und tu, was Du eigentlich tun wolltest.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von nufan » Mo Apr 05, 2010 1:38 pm

Xin hat geschrieben:
nufan hat geschrieben:
Xin hat geschrieben:und ich bin mir nicht 100%ig sicher, was Du willst.
In meinem Programm brauche ich einige externe Tools (apt, mount, rsync, chmod...). Es wäre ziemlich aufwändig immer die entsprechenden Bibliotheken zu verwenden. Das würde ewig dauern mich darin einzuarbeiten. Deshalb setze ich diese Programme voraus und installiere sie falls sie nicht vorhanden sind.
Dann installiere Dich, setz Dich ins richtige Verzeichnis und starte Dich selbst neu, stelle damit fest, dass Du alles installiert hast und tu, was Du eigentlich tun wolltest.
Hmm... ich glaube ich verstehe was du meinst, das mit dem Verzeichnis wechseln kann ich mir aber sparen:

Code: Alles auswählen

#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <iostream>

using namespace std;

int main (int argc, char *argv[])
{
  
  if (argc == 1)       // keine extra Parameter
  {
    // -> zweiten Prozess starten
    char *path = new char [strlen (argv[0]) + strlen (" -copy") + 1];   // Speicher für Pfad und Parameter reservieren
    char c;           // Hilfsvariable zum Einlesen
    strcpy (path, argv[0]);      // Pfad des Programms kopieren
    strcat (path, " -copy");      // Parameter dranhängen
 
    FILE *copy = popen (path, "r");  // Programm nochmal mit Parameter zum Lesen starten
    sleep (1);            // ein bisschen drauf warten (nicht schön gelöst...)
    
    while (feof (copy) == 0)          // solange was da ist lesen und ausgeben
    {
      c = fgetc (copy);
      if (isprint (c) != 0 || isspace (c) != 0)
        cout << c;
    }
    cout << endl;
    
    pclose (copy);       // anderen Prozess beenden
    delete[] path;       // Speicher wieder freigeben
  
  }
  
  else if (strcmp (argv[1], "-copy") == 0)   // Programm wurde mit Parametern aufgerufen -> zweiter Prozess
  {
    FILE *shell = popen ("sh", "w");       // sh zum Schreiben starten
    fprintf (shell, "ls");                        // irgendein Programm ausführen
    pclose (shell);                               // sh beenden
  }

  return 0;

}
Das ist sicher schön gelöst, aber ich kann nun mit dem ersten Programm keinen Einfluss mehr auf das zweite nehmen.

Nach etwas herumprobieren hab ich eine für mich perfekte Lösung gefunden:
main.cpp:

Code: Alles auswählen

#include <QApplication>
#include "ShellDialog.h"

int main (int argc, char *argv[])
{

  QApplication app (argc, argv);
  ShellDialog dialog;

  dialog.show ();

  return app.exec ();

}
CommandLine.h:

Code: Alles auswählen

#ifndef COMMANDLINE_H
#define COMMANDLINE_H

#include <QLineEdit>

// Widget to enter single line of text
class CommandLine : public QLineEdit
{

  Q_OBJECT

  public:
    CommandLine ();

  private slots:
    // emits the signal "comamndEntered ()" with the widget's text and clears the widget when enter is pressed
    void emitCommandSignal ();

  signals:
    // current text of the widget is passed; is used to pass a command to the shell
    void commandEntered (QString command);

};

#endif
CommandLine.cpp:

Code: Alles auswählen

#include "CommandLine.h"

CommandLine::CommandLine ()
{

  connect (this, SIGNAL (returnPressed()), this, SLOT (emitCommandSignal()));  // call slot when enter is pressed

}



void CommandLine::emitCommandSignal ()
{

  emit commandEntered (text ());       // emit signal with current text
  clear ();                            // clear the widget's text

}
Shell.h:

Code: Alles auswählen

#ifndef SHELL_H
#define SHELL_H

#include <QProcess>
#include <QString>

// creates a shell as new process
class Shell : public QProcess
{

  Q_OBJECT
  
  public:
    Shell ();     // starts and sets up the process
    ~Shell ();    // stops the process
  
  public slots:
    void exec (const QString& command);  // executes the passed command in the shell

  private slots:
    void showOutput ();  // emits signal to show output of process in a widget

  signals:
    void newText (QString text);  // output of the process is passed

};

#endif
Shell.cpp:

Code: Alles auswählen

#include "Shell.h"

Shell::Shell ()
{

  // signal with text is emitted when output is available
  connect (this, SIGNAL (readyReadStandardOutput()), this, SLOT (showOutput()));

  setProcessChannelMode (QProcess::MergedChannels); // merge stdout and stderr
  start ("sh");          // start shell
  waitForStarted ();     // give it time to start up

}



Shell::~Shell ()
{

  close ();        // stop the process

}



void Shell::exec (const QString& command)
{

  write ((command + '\n').toStdString ().c_str ());  // write command to the shell
  emit newText ("<b>$ " + command + "</b>");         // emit highlighted command to be appended to a widget

}



void Shell::showOutput ()
{

  emit newText (readAllStandardOutput());   // emit all available output of the process

}
ShellDialog.h:

Code: Alles auswählen

#ifndef SHELLDIALOG_H
#define SHELLDIALOG_H

#include "Shell.h"
#include "CommandLine.h"
#include <QWidget>
#include <QTextEdit>
#include <QVBoxLayout>

// dialog that has a commandline and a widget for output
class ShellDialog : public QWidget
{

  Q_OBJECT
  
  public:
    ShellDialog ();          // creates widgets and sets up connections
    ~ShellDialog ();
  
  private:
    QVBoxLayout *layout;      // layout for commandline and output widget
    QTextEdit *outputwidget;  // widget to show the output of the process
    CommandLine *commandline; // single line widget that emits signal when enter is pressed
    Shell sh;                 // shell started in a separate process

};

#endif
ShellDialog.cpp:

Code: Alles auswählen

#include "ShellDialog.h"

ShellDialog::ShellDialog ()
{

  // create widgets
  layout = new QVBoxLayout;
  commandline = new CommandLine;
  outputwidget = new QTextEdit;
  
  // user can select text, but not change it
  outputwidget -> setTextInteractionFlags (Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
  // add widgets to the layout
  layout -> addWidget (commandline);
  layout -> addWidget (outputwidget);
  setLayout (layout);                // apply layout to the widget
  setWindowTitle ("sh");

  // execute command entered in commandline in shell
  connect (commandline, SIGNAL (commandEntered (QString)), &sh, SLOT (exec (QString)));
  // append output of the shell-process to the output widget
  connect (&sh, SIGNAL (newText (QString)), outputwidget, SLOT (append(QString)));

}



ShellDialog::~ShellDialog ()
{

  delete outputwidget;
  delete commandline;
  delete layout;

}
And that's the Qt way to do it :)

Hier noch ein Bild von dem Programm in Aktion:
Bild

So... Problem endlich gelöst. Danke für die Hilfe, aber das war leider von mir der falsch Ansatz :)

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Xin » Mo Apr 05, 2010 4:36 pm

Magst Du daraus eine Wiki-Seite machen, wo man mal der Reihe Problem, Lösungsansatz und Code nachlesen kann?
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von nufan » Mo Apr 05, 2010 4:49 pm

Xin hat geschrieben:Magst Du daraus eine Wiki-Seite machen, wo man mal der Reihe Problem, Lösungsansatz und Code nachlesen kann?
Klar, werd ich machen. Ich hatte sowieso vor etwas Ähnliches im Rahmen des Qt-Tutorials zu zeigen. Nur soll das Aufgrund der Vorgeschichte eher ins Linux-FAQ, wegen der Lösung ins Qt-Tutorial oder ganz wo anders hin?

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Kerli » Mo Apr 05, 2010 5:14 pm

nufan hat geschrieben:Nur soll das Aufgrund der Vorgeschichte eher ins Linux-FAQ, wegen der Lösung ins Qt-Tutorial oder ganz wo anders hin?
Ich hab mir den Code nicht vollständig durchgeschaut, aber so wie es mir scheint ist das ganz doch sehr stark mit Qt verbunden. Wenn es möglich ist das ganz auf zwei Teile aufzuteilen, wo das Verwenden auch ohne Qt funktioniert würde ich es aufteilen und ansonsten ins Qt-Tutorial und ins Linux-FAQ einen Link setzen.
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Ausgabe von mit Pipe verbundenen Programm auffangen

Beitrag von Xin » Mo Apr 05, 2010 7:32 pm

nufan hat geschrieben:
Xin hat geschrieben:Magst Du daraus eine Wiki-Seite machen, wo man mal der Reihe Problem, Lösungsansatz und Code nachlesen kann?
Klar, werd ich machen. Ich hatte sowieso vor etwas Ähnliches im Rahmen des Qt-Tutorials zu zeigen. Nur soll das Aufgrund der Vorgeschichte eher ins Linux-FAQ, wegen der Lösung ins Qt-Tutorial oder ganz wo anders hin?
Ich dachte vorrangig an den Linux-Teil.

Der Qt-Teil ist letztendlich die Darstellung. Du kannst Dich im Qt-Teil dann doch auf Dein Linux-Tutorial beziehen. Dafür müssen die Probleme natürlich sauber voneinander getrennt sein, was sie aber sowieso sein sollten.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Antworten