web-dev-qa-db-fra.com

Comment exécuter une commande et obtenir le code retour stdout et stderr de la commande en C++

Étant donné la réponse suivante (première réponse c ++ 11):

Comment exécuter une commande et obtenir une sortie de commande dans C++ en utilisant POSIX?

Voici la mise en œuvre pour votre commodité:

#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;
}

Cela fonctionne vraiment bien pour exécuter une commande (par exemple std::string res = exec("ls");) et obtenir la sortie standard dans une chaîne.

Mais ce qu’il ne fait pas, c’est d’obtenir le code retour de la commande (entier de type succès/échec) ou le stderr. Idéalement, j'aimerais un moyen d'obtenir les trois (code retour, stdout, stderr).

Je me contenterais de stdout et stderr. Je pense que je dois ajouter un autre canal, mais je ne vois pas vraiment comment le premier canal est configuré pour obtenir la sortie standard. Je ne vois donc pas comment je le changerais pour obtenir les deux.

Quelqu'un a-t-il des idées sur la façon de procéder, ou des approches alternatives qui peuvent fonctionner?

mettre à jour

Voir mon exemple complet ici avec la sortie:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

Vous pouvez constater que 3 res: n'imprime pas stderr de la même manière que 2 res: stdout, mais stderr est simplement affiché à l'écran sur une ligne distincte par le processus (et non par mon programme).

Libs externes

Je ne souhaite vraiment pas utiliser de bibliothèques externes telles que Qt et boost - principalement parce que je veux la portabilité de celle-ci et que de nombreux projets sur lesquels je travaille n'utilisent pas boost. Cependant, je marquerai les solutions contenant ces options, car elles sont valables pour les autres utilisateurs :)

Solution complète en utilisant les commentaires/réponses

Merci à tous pour vos réponses/commentaires, voici la solution modifiée (et exécutable):

solution de travail

4
code_fodder

De la page de manuel de popen:

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

Donc, appeler vous-même pclose() (au lieu d’utiliser destructor-magic de std::shared_ptr<>) vous donnera le code de retour de votre processus (ou le bloquera si le processus n’est pas terminé).

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;
}

Avec stderr et stdout avec popen(), je crains que vous n’ayez besoin de rediriger la sortie de stderr vers stdout à partir de la ligne de commande que vous passez à popen () en ajoutant 2>&1. Cela a pour inconvénient que les deux flux sont mélangés de manière imprévisible.

Si vous voulez vraiment avoir deux descripteurs de fichier distincts pour stderr et stdout, vous pouvez le faire vous-même et dupliquer les nouveaux processus stdout/stderr dans deux canaux accessibles depuis le processus parent. (voir dup2() et pipe()). Je pourrais entrer dans les détails ici, mais c’est une façon assez fastidieuse de faire les choses et il faut faire très attention. Et Internet regorge d'exemples.

5
Patrick B.

Il y a une sorte de solution de contournement possible. Vous pouvez rediriger le stderr vers stdout en ajoutant "2> & 1" à votre cmd. Cela conviendrait-il à vos besoins?

3
Alex

Vous pouvez obtenir le code de retour du canal en utilisant un programme de suppression personnalisé en tant que tel:

#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