So führen Sie einen Befehl aus und erhalten den Rückgabecode stdout und stderr des Befehls in C++

Lesezeit: 6 Minuten

Benutzer-Avatar
code_futter

Angesichts der folgenden Antwort (erste c ++ 11-Antwort):

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

Hier ist die Implementierung für Ihre Bequemlichkeit:

#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>

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

Dies funktioniert wirklich gut, um einen Befehl auszuführen (z std::string res = exec("ls");) und erhalten Sie die stdout in eine Zeichenfolge.

Aber was es nicht tut, ist den Rückgabecode des Befehls (pass/fail integer) oder die stderr. Idealerweise hätte ich gerne eine Möglichkeit, alle drei zu bekommen (Rückgabecode, stdout, stderr).

Ich würde mich mit stdout und stderr zufrieden geben. Ich denke, dass ich eine weitere Pipe hinzufügen muss, aber ich kann nicht wirklich sehen, wie die erste Pipe eingerichtet ist, um stdout zu erhalten, also kann ich mir nicht vorstellen, wie ich sie ändern würde, um beide zu bekommen.

Hat jemand eine Idee, wie man das macht, oder alternative Ansätze, die funktionieren könnten?

aktualisieren

Siehe mein vollständiges Beispiel hier mit der Ausgabe:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

Sie können sehen, dass 3 res: druckt stderr nicht auf die gleiche Weise 2 res: stdout tut, aber stderr wird nur vom Prozess (und nicht von meinem Programm) in einer separaten Zeile auf den Bildschirm ausgegeben.

Externe Bibliotheken

Ich möchte wirklich keine externen Bibliotheken wie Qt und Boost verwenden – hauptsächlich, weil ich die Portabilität möchte und auch viele Projekte, an denen ich arbeite, Boost nicht verwenden. Ich werde jedoch Lösungen markieren, die diese Optionen enthalten, da sie für andere Benutzer gültig sind 🙂

Vollständige Lösung mit Kommentaren/Antworten

Vielen Dank an alle für Ihre Antworten / Kommentare, hier ist die modifizierte Lösung (und lauffähig):

funktionierende Lösung

  • Ist boost eine Option? Wenn ja, dann schauen Sie doch mal vorbei boost::prozess.

    – GR

    4. September 2018 um 11:11 Uhr

  • @ user1810087 hmm … nein, ich würde es vorziehen, es in reinem c++/c++11 zu behalten. In meinem speziellen Projekt haben wir uns entschieden, keine Boost-Bibliotheken zu verwenden.

    – Code_Futter

    4. September 2018 um 11:12 Uhr

  • Es gibt eine Art Problemumgehung möglich. Sie können stderr nach stdout umleiten, indem Sie “2>&1” an Ihr cmd anhängen. Würde das Ihren Bedürfnissen entsprechen?

    – Alex

    4. September 2018 um 11:13 Uhr


  • @Alex verdammt … das ist clever, ich benutze das die ganze Zeit in Bash-Skripten, habe aber nicht daran gedacht, es hier einzusetzen! … wenn es funktioniert, dann ja 🙂 .. Ich würde immer noch vorschlagen, eine C ++ – Version zu machen , aber das ist eine gute Workaround-Idee, Sir … jetzt testen …

    – Code_Futter

    4. September 2018 um 11:15 Uhr

  • @Alex das hat gut funktioniert – bitte fügen Sie Ihren Kommentar als Antwort hinzu und ich werde darüber abstimmen

    – Code_Futter

    4. September 2018 um 11:40 Uhr

Benutzer-Avatar
Patrick B.

Aus der Manpage von popen:

The pclose() function waits for the associated process to terminate  and returns the exit status of the command as returned by wait4(2).

Also anrufen pclose() selbst (anstatt zu verwenden std::shared_ptr<>‘s Destructor-Magic) gibt Ihnen den Rückgabecode Ihres Prozesses (oder blockiert, wenn der Prozess nicht beendet wurde).

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;

    auto pipe = popen(cmd, "r"); // get rid of shared_ptr

    if (!pipe) throw std::runtime_error("popen() failed!");

    while (!feof(pipe)) {
        if (fgets(buffer.data(), 128, pipe) != nullptr)
            result += buffer.data();
    }

    auto rc = pclose(pipe);

    if (rc == EXIT_SUCCESS) { // == 0

    } else if (rc == EXIT_FAILURE) {  // EXIT_FAILURE is not used by all programs, maybe needs some adaptation.

    }
    return result;
}

Stderr und stdout bekommen mit popen()ich fürchte, Sie müssten die Ausgabe von stderr von der Befehlszeile, die Sie an popen() übergeben, durch Hinzufügen auf stdout umleiten 2>&1. Dies hat den Nachteil, dass beide Ströme unvorhersehbar gemischt werden.

Wenn Sie wirklich zwei unterschiedliche Dateideskriptoren für stderr und stdout haben möchten, besteht eine Möglichkeit darin, das Forking selbst durchzuführen und die neuen Prozesse stdout/stderr in zwei Pipes zu duplizieren, auf die vom übergeordneten Prozess zugegriffen werden kann. (sehen dup2() und pipe()). Ich könnte hier mehr ins Detail gehen, aber das ist eine ziemlich mühsame Art, Dinge zu tun, und es muss viel Sorgfalt aufgewendet werden. Und das Internet ist voll von Beispielen.

  • Patrick – könnten Sie ein Beispielcode-Snippet posten, wie ich das ändern würde? Ich nehme an, ich muss nur a hinzufügen int res = pclose() Am Ende?

    – Code_Futter

    4. September 2018 um 11:18 Uhr

  • Ich habe Ihren Code genommen und die Verwendung von entfernt std::shared_ptr.

    – Patrick B.

    4. September 2018 um 11:23 Uhr


  • Danke – alles funktioniert!, eine vollständige Lösung am Ende meines Beitrags gepostet 🙂

    – Code_Futter

    4. September 2018 um 11:41 Uhr

  • Wie kann ich eine .bat-Datei anstelle des cmd-Arguments übergeben?

    – kittu

    20. Mai 2020 um 18:40 Uhr


Es gibt eine Art Problemumgehung möglich. Sie können stderr nach stdout umleiten, indem Sie “2>&1” an Ihr cmd anhängen. Würde das Ihren Bedürfnissen entsprechen?

Sie können den Rückgabecode aus der Pipe abrufen, indem Sie einen benutzerdefinierten Löscher wie einen solchen verwenden:

#include <cstdio>
#include <iostream>
#include <memory>
#include <string>
#include <array>
#include <utility>

using namespace std;
pair<string, int> exec(const char* cmd) {
    array<char, 128> buffer;
    string result;
    int return_code = -1;
    auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); };
    { // scope is important, have to make sure the ptr goes out of scope first
    const unique_ptr<FILE, decltype(pclose_wrapper)> pipe(popen(cmd, "r"), pclose_wrapper);
    if (pipe) {
        while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    }
    return make_pair(result, return_code);
}

int main(int argc, char* argv[]) {
    if (argc <= 1) return 0;
    cout << "calling with " << argv[1] << '\n';
    const auto process_ret = exec(argv[1]);
    cout << "captured stdout : " << '\n' << process_ret.first << endl;
    cout << "program exited with status code " << process_ret.second << endl;
    return 0;
} 

Benutzer-Avatar
Held Daniel

Unten ist eine etwas andere Version von Patrick. B-Antwort, das nutzt fread() Anstatt von fgets().

Die Verwendung von fread() wurde von Antti Haapala in diesem Thread vorgeschlagen:

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

Diese Version liest stdout in a std::stringdie als 2. Argument übergeben werden muss.

Die Funktion gibt den Exit-Code der Ausführung von zurück cmdwie zurückgegeben von pclose().

Auch zu bekommen stderram Ende hinzufügen cmd: "2>&1"wie von Alex in diesem Thread vorgeschlagen.

main() Funktion unten, zeigt, wie man verwendet:

int execute(std::string cmd, std::string& output)

um das aktuelle Verzeichnis aufzulisten und auf stdout auszugeben:

#include <iostream>
#include <array>
#include <unistd.h>
    
int execute(std::string cmd, std::string& output) {
    const int bufsize=128;
    std::array<char, bufsize> buffer;

    auto pipe = popen(cmd.c_str(), "r");
    if (!pipe) throw std::runtime_error("popen() failed!");

    size_t count;
    do {
        if ((count = fread(buffer.data(), 1, bufsize, pipe)) > 0) {
            output.insert(output.end(), std::begin(buffer), std::next(std::begin(buffer), count));
        }
    } while(count > 0);

    return pclose(pipe);
}    
    
int main(int argc, char** argv) {
std::string output;

    execute("ls", output);
    std::cout << output;
}

1015530cookie-checkSo führen Sie einen Befehl aus und erhalten den Rückgabecode stdout und stderr des Befehls in C++

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy