web-dev-qa-db-ja.com

system()コマンドからの標準出力の最適なキャプチャ

system()を介して外部アプリケーションを起動しようとしています-たとえば、system("ls")。発生した出力をキャプチャして、さらに処理するために別の関数に送信できるようにします。 C/C++でそれを行う最良の方法は何ですか?

52
SinisterDex

Popenマニュアルから:

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);
40
jkramer

Popen()関数を試してください。 system()などのコマンドを実行しますが、出力を新しいファイルに送信します。ストリームへのポインターが返されます。

  FILE *lsofFile_p = popen("lsof", "r");

  if (!lsofFile_p)
  {
    return -1;
  }

  char buffer[1024];
  char *line_p = fgets(buffer, sizeof(buffer), lsofFile_p);
  pclose(lsofFile_p);
32
Andrew Langrick

編集:別の関数ではなく、別のプログラムに出力を渡したいという質問を誤解しています。 popen()はほぼ間違いなくあなたが望むものです。

システムはシェルへのフルアクセスを提供します。引き続き使用したい場合は、system( "ls> tempfile.txt")によって出力を一時ファイルにリダイレクトできますが、安全な一時ファイルを選択するのは大変です。または、別のプログラムを介してリダイレクトすることもできます:system( "ls | otherprogram");

Popen()コマンドを推奨する人もいます。これは、出力を自分で処理できる場合に必要なものです。

FILE *output = popen("ls", "r");

これにより、コマンドの出力を読み取ることができるFILEポインターが得られます。

Pipe()呼び出しを使用してfork()と組み合わせて接続を作成し、新しいプロセスを作成し、dup2()を使用してそれらの標準入出力を変更し、exec()を使用して新しいプログラムを実行し、wait()メインプログラムでそれらを待ちます。これは、シェルと同じようにパイプラインを設定するだけです。詳細と例については、pipe()のマニュアルページを参照してください。

7
wnoise

関数popen()などは、stderrなどをリダイレクトしません。そのために popen3() と書きました。

これは私のpopen3()のbowdlerisedバージョンです:

int popen3(int fd[3],const char **const cmd) {
        int i, e;
        int p[3][2];
        pid_t pid;
        // set all the FDs to invalid
        for(i=0; i<3; i++)
                p[i][0] = p[i][1] = -1;
        // create the pipes
        for(int i=0; i<3; i++)
                if(pipe(p[i]))
                        goto error;
        // and fork
        pid = fork();
        if(-1 == pid)
                goto error;
        // in the parent?
        if(pid) {
                // parent
                fd[STDIN_FILENO] = p[STDIN_FILENO][1];
                close(p[STDIN_FILENO][0]);
                fd[STDOUT_FILENO] = p[STDOUT_FILENO][0];
                close(p[STDOUT_FILENO][1]);
                fd[STDERR_FILENO] = p[STDERR_FILENO][0];
                close(p[STDERR_FILENO][1]);
                // success
                return 0;
        } else {
                // child
                dup2(p[STDIN_FILENO][0],STDIN_FILENO);
                close(p[STDIN_FILENO][1]);
                dup2(p[STDOUT_FILENO][1],STDOUT_FILENO);
                close(p[STDOUT_FILENO][0]);
                dup2(p[STDERR_FILENO][1],STDERR_FILENO);
                close(p[STDERR_FILENO][0]);
                // here we try and run it
                execv(*cmd,const_cast<char*const*>(cmd));
                // if we are there, then we failed to launch our program
                perror("Could not launch");
                fprintf(stderr," \"%s\"\n",*cmd);
                _exit(EXIT_FAILURE);
        }

        // preserve original error
        e = errno;
        for(i=0; i<3; i++) {
                close(p[i][0]);
                close(p[i][1]);
        }
        errno = e;
        return -1;
}
3
Will

最も効率的な方法は、stdoutストリームをバイパスして、FILEファイル記述子を直接使用することです。

_pid_t popen2(const char *command, int * infp, int * outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) == -1)
        return -1;

    if (pipe(p_stdout) == -1) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        return -1;
    }

    pid = fork();

    if (pid < 0) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        close(p_stdout[0]);
        close(p_stdout[1]);
        return pid;
    } else if (pid == 0) {
        close(p_stdin[1]);
        dup2(p_stdin[0], 0);
        close(p_stdout[0]);
        dup2(p_stdout[1], 1);
        dup2(::open("/dev/null", O_WRONLY), 2);

        /// Close all other descriptors for the safety sake.
        for (int i = 3; i < 4096; ++i) {
            ::close(i);
        }

        setsid();
        execl("/bin/sh", "sh", "-c", command, NULL);
        _exit(1);
    }

    close(p_stdin[0]);
    close(p_stdout[1]);

    if (infp == NULL) {
        close(p_stdin[1]);
    } else {
        *infp = p_stdin[1];
    }

    if (outfp == NULL) {
        close(p_stdout[0]);
    } else {
        *outfp = p_stdout[0];
    }

    return pid;
}
_

childから出力を読み取るには、次のようにpopen2()を使用します。

_int child_stdout = -1;
pid_t child_pid = popen2("ls", 0, &child_stdout);

if (!child_pid) {
    handle_error();
}

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));
_

書き込みと読み取りの両方:

_int child_stdin = -1;
int child_stdout = -1;
pid_t child_pid = popen2("grep 123", &child_stdin, &child_stdout);

if (!child_pid) {
    handle_error();
}

const char text = "1\n2\n123\n3";
ssize_t bytes_written = write(child_stdin, text, sizeof(text) - 1);

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));
_
2
GreenScape

Windowsでは、system()を使用する代わりに、CreateProcessを使用して、出力をパイプにリダイレクトし、パイプに接続します。

これはPOSIXの方法でも可能だと思いますか?

1
shoosh

関数popen()pclose()はあなたが探しているものです。

例として glibcマニュアル をご覧ください。

1

このページでは: capture_the_output_of_a_child_process_in_c はpopenを使用する場合とfork/exec/dup2/STDOUT_FILENOアプローチを使用する場合の制限について説明しています。

問題が発生しています capentshark出力とpopen .

そして、私はこの制限が私の問題かもしれないと推測しています:

生のファイル記述子ではなく、stdioストリームを返します。これは、出力を非同期的に処理するには不適切です。

他のアプローチで解決策があれば、この答えに戻ります。

1
nephewtom

実際に、私はチェックしたばかりです:

  1. プロセスが分岐しているため、popenには問題があります。したがって、Shellコマンドが実行されるのを待つ必要がある場合、それを逃す危険があります。私の場合、パイプが機能する前にプログラムが終了しました。

  2. Linuxでtarコマンドを使用してsystem呼び出しを使用することになりました。 systemからの戻り値は、tarの結果でした。

だから:戻り値が必要な場合、それからpopenを使用する必要がないだけでなく、おそらくあなたが望むことをしないでしょう。

1
mousomer

2つの異なるプロセスは通常メモリ空間を共有しないため、標準Cで可能かどうかは完全にはわかりません。私が考えられる最も簡単な方法は、2番目のプログラムがその出力をテキストファイル(programname> textfile.txt)にリダイレクトし、そのテキストファイルを読み込んで処理することです。しかし、それは最善の方法ではないかもしれません。

0
Nicholas Flynt