it-swarm.com.de

Ausführen eines Befehls und Abrufen des Rückkehrcodes stdout und stderr des Befehls in C++

Angesichts der folgenden Antwort (erste Antwort von C++ 11):

Wie kann ich einen Befehl ausführen und die Ausgabe des Befehls innerhalb von C++ mit POSIX abrufen?

Hier ist die Implementierung für Ihren Komfort:

#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 sehr gut, um einen Befehl auszuführen (z. B. std::string res = exec("ls");) und den Standardwert in einen String zu bekommen.

Was es aber nicht tut, ist den Befehlsrückgabecode (pass/fail integer) oder den stderr zu erhalten. Idealerweise würde ich gerne einen Weg finden, um alle drei zu erhalten (Returncode, stdout, stderr).

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

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

update

Siehe mein komplettes Beispiel hier mit der Ausgabe:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

Sie können sehen, dass 3 res: stderr nicht auf dieselbe Weise druckt wie 2 res: stdout, aber stderr wird vom Prozess (und nicht meinem Programm) in einer separaten Zeile auf dem Bildschirm ausgegeben.

Externe Libs

Ich möchte wirklich keine externen Bibliotheken wie Qt und Boost verwenden - meistens, weil ich die Portabilität davon will und auch viele Projekte, an denen ich arbeite, keinen Boost verwenden. Ich werde jedoch Lösungen markieren, die diese Optionen enthalten, da sie für andere Benutzer gültig sind :)

Komplette Lösung mit Kommentar/Antwort

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

Arbeitslösung

4
code_fodder

Auf 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).

Wenn Sie pclose() selbst aufrufen (anstelle der Destruktor-Magie von std::shared_ptr<>), erhalten Sie den Rückgabecode Ihres Prozesses (oder blockieren, 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 mit popen() bekommen, ich fürchte, Sie müssen die Ausgabe von stderr von der Befehlszeile, die Sie an popen () übergeben, durch die Eingabe von 2>&1 in stdout umleiten. Dies hat die Unannehmlichkeit, dass beide Ströme unvorhersehbar gemischt werden.

Wenn Sie wirklich zwei unterschiedliche Datei-Deskriptoren für stderr und stdout haben möchten, können Sie dies tun, indem Sie sich selbst fälschen und die neuen Prozesse stdout/stderr auf zwei Pipes kopieren, die vom übergeordneten Prozess aus zugänglich sind. (Siehe dup2() und pipe()). Ich könnte hier näher eingehen, aber dies ist eine ziemlich langwierige Art, Dinge zu tun, und es ist viel Vorsicht geboten. Und das Internet ist voller Beispiele.

5
Patrick B.

Es ist eine Art Problemumgehung möglich. Sie können den Ordner nach stdout umleiten, indem Sie "2> & 1" an Ihre cmd anhängen. Würde dies Ihren Bedürfnissen entsprechen?

3
Alex

Sie können den Rückgabewert aus der Pipe abrufen, indem Sie einen benutzerdefinierten Deleter 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;
} 
0
Zhou Shao