607 Stimmen

Wie führe ich einen Befehl aus und erhalte die Ausgabe des Befehls innerhalb von C++ mit POSIX?

Ich suche nach einem Weg, um die Ausgabe eines Befehls zu erhalten, wenn er aus einem C++-Programm heraus ausgeführt wird. Ich habe versucht, die system()-Funktion zu verwenden, aber das führt nur einen Befehl aus. Hier ist ein Beispiel dafür, was ich suche:

std::string result = system("./some_command");

Ich muss einen beliebigen Befehl ausführen und seine Ausgabe erhalten. Ich habe mir boost.org angesehen, aber nichts gefunden, das mir das geben würde, was ich brauche.

3 Stimmen

Auch siehe Antworten in dieser Frage: https://stackoverflow.com/questions/52164723/how-to‌​-execute-a-command-a‌​nd-get-return-code-s‌​tdout-and-stderr-of-‌​command-in-c für eine Erweiterung der großartigen Antwort unten, die Methoden zum Abrufen des Rückgabecodes und von stderr sowie stdout bereitstellt, die diese Antwort bereits erklärt.

8 Stimmen

@code_fodder Sie können einen Link zu stackoverflow.com/questions/52164723/… erstellen.

802voto

waqas Punkte 9803
#include 
#include 
#include 
#include 
#include 
#include 

std::string exec(const char* cmd) {
    std::array buffer;
    std::string result;
    std::unique_ptr pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

Vor C++11 Version:

#include 
#include 
#include 
#include 

std::string exec(const char* cmd) {
    char buffer[128];
    std::string result = "";
    FILE* pipe = popen(cmd, "r");
    if (!pipe) throw std::runtime_error("popen() failed!");
    try {
        while (fgets(buffer, sizeof buffer, pipe) != NULL) {
            result += buffer;
        }
    } catch (...) {
        pclose(pipe);
        throw;
    }
    pclose(pipe);
    return result;
}

Ersetzen Sie popen und pclose durch _popen und _pclose für Windows.

101 Stimmen

Bitte beachten Sie, dass dies nur stdout und nicht stderr abgreifen wird.

16 Stimmen

Bitte beachten Sie auch, dass eine Ausnahme in result += buffer auftreten kann, sodass das Pipe möglicherweise nicht ordnungsgemäß geschlossen wird.

0 Stimmen

@larsmans Aber das liegt an einem Codefehler außerhalb dieser Funktion, richtig, nicht an etwas, das erwartet werden könnte?

103voto

Jonathan Wakely Punkte 159417

Das Lesen von sowohl stdout als auch stderr (und auch das Schreiben von stdin, das hier nicht gezeigt wird) ist mit meinem pstreams-Header ganz einfach, der iostream-Klassen definiert, die ähnlich wie popen funktionieren:

#include 
#include 
#include 

int main()
{
  // Prozess ausführen und ein streambuf erstellen, der dessen stdout und stderr liest
  redi::ipstream proc("./some_command", redi::pstreams::pstdout | redi::pstreams::pstderr);
  std::string line;
  // Lesen von stdout des Kindprozesses
  while (std::getline(proc.out(), line))
    std::cout << "stdout: " << line << '\n';
  // Wenn das Lesen von stdout am EOF gestoppt wurde, dann den Zustand zurücksetzen:
  if (proc.eof() && proc.fail())
    proc.clear();
  // Lesen von stderr des Kindprozesses
  while (std::getline(proc.err(), line))
    std::cout << "stderr: " << line << '\n';
}

0 Stimmen

Popen ist die richtige Lösung, wenn entweder eine Eingabe- oder Ausgabekommunikation vom Kindprozess gewünscht wird, aber nicht beides.

32 Stimmen

Ich widerspreche. popen erfordert die Verwendung der C-Stdio-API, ich bevorzuge die iostreams-API. popen erfordert, dass Sie den FILE-Handle manuell aufräumen, pstreams erledigen das automatisch. popen akzeptiert nur ein const char* für das Argument, was sorgfältig zu vermeiden ist, um Shell-Injection-Angriffe zu vermeiden, pstreams ermöglichen es Ihnen, einen Vektor von Zeichenfolgen ähnlich wie execv zu übergeben, was sicherer ist. popen gibt Ihnen nur eine Pipe, pstreams teilen Ihnen die PID des Kindes mit, so dass Sie Signale senden können, z.B. um es zu beenden, wenn es blockiert ist oder nicht beendet. Alle diese sind Vorteile, selbst wenn Sie nur unidirektionale IO möchten.

1 Stimmen

Ein weiteres Problem bei dieser Lösung tritt auf, wenn das Kind genug in stderr schreibt, um die Puffer zu füllen und zu blockieren, bevor es anfängt, in stdout zu schreiben. Der Elternteil wird blockiert, wenn er stdout liest, während das Kind auf das Lesen von stderr wartet. Ressourcenverriegelung! Wenigstens eine dieser Schleifen sollte besser asynchron (d.h. in Threads) sein.

47voto

TarmoPikaro Punkte 4208

Für Windows funktioniert popen auch, aber es öffnet ein Konsolenfenster - das schnell über Ihrer UI-Anwendung blinkt. Wenn Sie professionell sein wollen, ist es besser, dieses "Blinken" zu deaktivieren (besonders wenn der Endbenutzer es abbrechen kann).

Hier ist also meine eigene Version für Windows:

(Dieser Code wurde teilweise aus Ideen, die in The Code Project und MSDN-Beispielen geschrieben wurden, neu kombiniert.)

#include 
#include 
//
// Ausführen eines Befehls und Ergebnisse erhalten. (Nur Standardausgabe)
//
CStringA ExecCmd(
    const wchar_t* cmd              // [in] auszuführender Befehl
)
{
    CStringA strResult;
    HANDLE hPipeRead, hPipeWrite;

    SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES)};
    saAttr.bInheritHandle = TRUE; // Pipe-Handles werden vom untergeordneten Prozess übernommen.
    saAttr.lpSecurityDescriptor = NULL;

    // Erstellen einer Pipe, um Ergebnisse von stdout des untergeordneten Prozesses zu erhalten.
    if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
        return strResult;

    STARTUPINFOW si = {sizeof(STARTUPINFOW)};
    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdOutput  = hPipeWrite;
    si.hStdError   = hPipeWrite;
    si.wShowWindow = SW_HIDE; // Verhindert das Blinken des cmd-Fensters.
                              // Erfordert STARTF_USESHOWWINDOW in dwFlags.

    PROCESS_INFORMATION pi = { 0 };

    BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    if (! fSuccess)
    {
        CloseHandle(hPipeWrite);
        CloseHandle(hPipeRead);
        return strResult;
    }

    bool bProcessEnded = false;
    for (; !bProcessEnded ;)
    {
        // Etwas Zeitabschnitt geben (50 ms), damit wir nicht 100% CPU verschwenden.
        bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;

        // Auch wenn der Prozess beendet ist - wir setzen das Lesen fort, wenn Daten über die Pipe verfügbar sind.
        for (;;)
        {
            char buf[1024];
            DWORD dwRead = 0;
            DWORD dwAvail = 0;

            if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
                break;

            if (!dwAvail) // Keine Daten verfügbar, zurückkehren
                break;

            if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
                // Fehler, der untergeordnete Prozess könnte beendet sein
                break;

            buf[dwRead] = 0;
            strResult += buf;
        }
    } //for

    CloseHandle(hPipeWrite);
    CloseHandle(hPipeRead);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return strResult;
} //ExecCmd

1 Stimmen

Dies ist meine bevorzugte Lösung für Windows, ich hoffe, Sie verzeihen mir meine Änderungen. Ich würde vorschlagen, das const-cast expliziter zu gestalten, während ich die explizite Verwendung von wchar_t und CreateProcessW als eine unnötige Beschränkung betrachte.

0 Stimmen

Sehen Sie ein Problem oder mögliches Problem mit diesem Cast? Ich bevorzuge es, den Code auf ein Minimum zu reduzieren und ihn nicht unnötig zu schreiben.

6 Stimmen

Nach dem Lesen der CreateProcess-Funktion (Windows) sehe ich eine echte Gefahr darin, dies zu tun: Die Unicode-Version dieser Funktion, CreateProcessW, kann den Inhalt dieses Strings ändern. Daher kann dieser Parameter kein Zeiger auf schreibgeschützten Speicher sein (wie zum Beispiel eine const-Variable oder ein Literalstring). Wenn dieser Parameter ein konstanter String ist, kann die Funktion einen Zugriffsverletzung verursachen. Es ist also vielleicht besser, die Befehlszeile zuerst in einen separaten Puffer zu kopieren, um zu verhindern, dass der Aufrufer seine originale Eingabe geändert bekommt.

39voto

Mr.Ree Punkte 8112

Ich würde popen() benutzen (++waqas).

Aber manchmal benötigen Sie Lese- und Schreibzugriff...

Es scheint, als würde niemand mehr die Dinge auf die harte Tour machen.

(Unter der Annahme einer Unix/Linux/Mac-Umgebung oder vielleicht Windows mit einer POSIX-Kompatibilitätsschicht...)

enum PIPE_FILE_DESCRIPTERS
{
  READ_FD  = 0,
  WRITE_FD = 1
};

enum CONSTANTS
{
  BUFFER_SIZE = 100
};

int
main()
{
  int       parentToChild[2];
  int       childToParent[2];
  pid_t     pid;
  string    dataReadFromChild;
  char      buffer[BUFFER_SIZE + 1];
  ssize_t   readResult;
  int       status;

  ASSERT_IS(0, pipe(parentToChild));
  ASSERT_IS(0, pipe(childToParent));

  switch (pid = fork())
  {
    case -1:
      FAIL("Fork failed");
      exit(-1);

    case 0: /* Kind */
      ASSERT_NOT(-1, dup2(parentToChild[READ_FD], STDIN_FILENO));
      ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDOUT_FILENO));
      ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDERR_FILENO));
      ASSERT_IS(0, close(parentToChild [WRITE_FD]));
      ASSERT_IS(0, close(childToParent [READ_FD]));

      /*     file, arg0, arg1,  arg2 */
      execlp("ls", "ls", "-al", "--color");

      FAIL("Diese Zeile sollte niemals erreicht werden!!!");
      exit(-1);

    default: /* Eltern */
      cout << "Kind " << pid << " Prozess läuft..." << endl;

      ASSERT_IS(0, close(parentToChild [READ_FD]));
      ASSERT_IS(0, close(childToParent [WRITE_FD]));

      while (true)
      {
        switch (readResult = read(childToParent[READ_FD],
                                  buffer, BUFFER_SIZE))
        {
          case 0: /* Ende-der-Datei oder nicht-blockierendes Lesen. */
            cout << "Ende der Datei erreicht..."         << endl
                 << "Empfangene Daten ("
                 << dataReadFromChild.size() << "): " << endl
                 << dataReadFromChild                << endl;

            ASSERT_IS(pid, waitpid(pid, & status, 0));

            cout << endl
                 << "Kind Exit-Status ist:  " << WEXITSTATUS(status) << endl
                 << endl;

            exit(0);

          case -1:
            if ((errno == EINTR) || (errno == EAGAIN))
            {
              errno = 0;
              break;
            }
            else
            {
              FAIL("read() fehlgeschlagen");
              exit(-1);
            }

          default:
            dataReadFromChild . append(buffer, readResult);
            break;
        }
      } /* während (true) */
  } /* switch (pid = fork())*/
}

Sie könnten auch mit select() und nicht-blockierenden Lesevorgänge herumspielen.

fd_set          readfds;
struct timeval  timeout;

timeout.tv_sec  = 0;    /* Sekunden */
timeout.tv_usec = 1000; /* Mikrosekunden */

FD_ZERO(&readfds);
FD_SET(childToParent[READ_FD], &readfds);

switch (select (1 + childToParent[READ_FD], &readfds, (fd_set*)NULL, (fd_set*)NULL, & timeout))
{
  case 0: /* Zeitüberschreitung */
    break;

  case -1:
    if ((errno == EINTR) || (errno == EAGAIN))
    {
      errno = 0;
      break;
    }
    else
    {
      FAIL("Select() fehlgeschlagen");
      exit(-1);
    }

  case 1:  /* Wir haben Eingabe */
    readResult = read(childToParent[READ_FD], buffer, BUFFER_SIZE);
    // Wie auch immer Sie es bearbeiten möchten...
    break;

  default:
    FAIL("Wie konnten wir Eingabe auf mehr als einem Dateideskriptor sehen?");
    exit(-1);
}

2 Stimmen

Der schwierige Weg ist richtig :) Mir gefällt die Idee mit dem select() Aufruf, obwohl ich in diesem Fall tatsächlich darauf warten muss, bis die Aufgabe abgeschlossen ist. Ich werde diesen Code für ein anderes Projekt behalten, das ich habe :)

4 Stimmen

... oder Sie könnten die vorhandene posix_spawnp Funktion verwenden

7 Stimmen

Dein execlp-Aufruf hat einen Fehler: Der letzte übergebene arg-Zeiger muss (char *) NULL sein, um die variable Argumentliste ordnungsgemäß zu terminieren (siehe execlp(3) zur Referenz).

20voto

paxdiablo Punkte 809679

Zwei mögliche Ansätze:

  1. Ich glaube nicht, dass popen() Teil des C++-Standards ist (es gehört meiner Erinnerung nach zu POSIX), aber es ist auf jedem UNIX-System verfügbar, mit dem ich gearbeitet habe (und du scheinst UNIX anzuzielen, da dein Befehl ./some_command ist).

  2. Falls es kein popen() geben sollte, kannst du system("./some_command >/tmp/some_command.out"); verwenden und dann die normalen Ein-/Ausgabe-Funktionen nutzen, um die Ausgabedatei zu verarbeiten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X