web-dev-qa-db-ja.com

コマンドを実行し、C ++でコマンドの戻りコードstdoutおよびstderrを取得する方法

次の答えが与えられた場合(最初のc ++ 11答え):

POSIXを使用してC++内でコマンドを実行し、コマンドの出力を取得する方法

ここにあなたの便宜のための実装があります:

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

これは、コマンド(std::string res = exec("ls");など)を実行してstdoutを文字列に取得するのに非常にうまく機能します。

しかし、それがしないことは、コマンドの戻りコード(合格/不合格の整数)またはstderrを取得することです。理想的には、3つすべて(戻りコード、stdout、stderr)を取得する方法が欲しいです。

私はstdoutとstderrを受け入れます。別のパイプを追加する必要があると考えていますが、stdoutを取得するために最初のパイプがどのように設定されているのか実際にはわかりません。

誰もがそれを行う方法や、機能する可能性のある代替アプローチを持っていますか?

更新

私の完全な例を参照してください ここ 出力なし:

_Start
1 res: /home

2 res: stdout

stderr
3 res: 
End
_

_3 res:_は、_2 res: stdout_と同じようにstderrを出力しないことがわかりますが、stderrは、(プログラムではなく)プロセスによって別の行で画面にダンプされるだけです。

外部ライブラリ

私は本当にQtやboostのような外部ライブラリを使用したくない-それは主にそれの移植性と私が取り組んでいる多くのプロジェクトがboostを使用していないためです。ただし、これらのオプションを含むソリューションは他のユーザーに有効であるため、マークアップします:)

コメント/回答を使用した完全なソリューション

あなたの答え/コメントをありがとう、ここに修正されたソリューションがあります(そして実行可能です):

working-solution

8
code_fodder

popenのmanページから:

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

そのため、_std::shared_ptr<>_のデストラクタマジックを使用する代わりに、pclose()を自分で呼び出すと、プロセスの戻りコード(またはプロセスが終了していない場合はブロック)が返されます。

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

popen()を使用してstderrとstdoutを取得する場合、_2>&1_を追加して、stderrの出力をpopen()に渡すコマンドラインからstdoutにリダイレクトする必要があると思います。これには、両方のストリームが予期せずに混在するという不便があります。

Stderrとstdoutの2つの区別されたファイル記述子が本当に必要な場合、1つの方法は、フォークを自分で実行し、新しいプロセスstdout/stderrを親プロセスからアクセス可能な2つのパイプに複製することです。 (dup2()およびpipe()を参照)。ここでさらに詳しく説明することもできますが、これは退屈な方法であり、十分な注意が必要です。そしてインターネットは例でいっぱいです。

10
Patrick B.

可能な回避策があります。コマンドに「2>&1」を追加することにより、stderrをstdoutにリダイレクトできます。これはあなたのニーズに合いますか?

3
Alex

パイプから戻りコードを取得するには、次のようにカスタム削除機能を使用します。

#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;
} 
2
Zhou Shao