stdoutとstderrをログファイルにコピーし、コンソールにstderr出力のみを表示し、stderrメッセージを別のファイルに書き込みます。

stdoutとstderrをログファイルにコピーし、コンソールにstderr出力のみを表示し、stderrメッセージを別のファイルに書き込みます。

次のようにbashスクリプトで出力リダイレクトを実行する必要がある状況があります。

  1. コンソールにはエラーメッセージのみが表示されます。
  2. エラーメッセージは、errout.logなどの特定のファイルにも記録する必要があります。
  3. 標準出力とエラーメッセージはどちらもallout.logなどのファイルに書き込む必要があります。
  4. それほど重要ではなく、持っていればいいのですが、おそらく多くの努力をかけて達成できるでしょう。 allout.logの順序はおそらく非常に似ているか、メッセージが表示される順序とまったく同じです。

他の同様の質問と回答でまったく同じ状況が見つかりませんでした。

答え1

zshではfoo 2>&2 2> err.log > all.log 2>&1

Bashではこれがうまくいくかもしれません:

foo 2>&1 >> all.log | tee -a all.log err.log >&2

または

{ foo >> all.log; } 2>&1 | tee -a all.log err.log >&2

答え2

シェルベースのソリューションが考えられないので、これをお勧めします。他の人々はより良​​いシェルベースのソリューションを提供しました。誰もが興味深い場合に備えてここに残しておきますが、他の答えがより良いです。

以下は、タスクを実行するCプログラムです。

#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

// Some wrappers around system calls to hide error handling
static void Pipe(int pipe_fds[]);
static pid_t Fork();
static void Dup2(int old_fd, int new_fd);
static void Execvp(const char *file, char *const argv[]);

int main(int argc, char* argv[])
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <script> [<arg>, ...]\n", argv[0]);
        return EXIT_FAILURE;
    }

    // Create a pipe to handle stdout
    int out_pipe[2];
    Pipe(out_pipe);

    // Create a second pipe to handle stderr
    int err_pipe[2];
    Pipe(err_pipe);

    if (Fork() == 0) { // Child
        // Wire the child's stdout stream to the write end of out_pipe
        close(out_pipe[0]);
        Dup2(out_pipe[1], STDOUT_FILENO);
        close(out_pipe[1]);

        // Wire the child's stderr stream to the write end of err
        close(err_pipe[0]);
        Dup2(err_pipe[1], STDERR_FILENO);
        close(err_pipe[1]);

        // Invoke the target program with the remaining args
        Execvp(argv[1], argv + 1);
    }

    // Parent only from here on out

    // Close the write ends of the pipes
    close(out_pipe[1]);
    close(err_pipe[1]);

    // Set up the file descriptors and events we're interested in monitoring
    struct pollfd poll_fds[] = {
        { .fd = out_pipe[0], .events = POLLIN },
        { .fd = err_pipe[0], .events = POLLIN },
    };

    int incomplete_fds = 0;

    while (incomplete_fds != 2 && poll(poll_fds, 2, -1) > 0) {
        for (int i = 0; i < 2; ++i) {
            // Is this file descriptor readable?
            if (poll_fds[i].revents & POLLIN) {
                char buffer[4096];
                const ssize_t num_bytes = read(poll_fds[i].fd, buffer, sizeof(buffer));

                // (3) write both stdout and stderr our stderr
                write(STDERR_FILENO, buffer, num_bytes);

                if (i == 1) { // if this was a write to stderr
                    // (1) write standard error to out stdout
                    write(STDOUT_FILENO, buffer, num_bytes);
                }

            }

            if (poll_fds[i].revents & (POLLHUP | POLLNVAL)) {
                // Don't expect anything more from this
                poll_fds[i].events = 0;
                poll_fds[i].fd = -1;
                ++incomplete_fds;
            }
        }
    }

    return EXIT_SUCCESS;
}

static void Pipe(int pipe_fds[])
{
    if (pipe(pipe_fds) < 0) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
}

static pid_t Fork()
{
    const pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    return pid;
}

static void Dup2(const int old_fd, const int new_fd)
{
    if (dup2(old_fd, new_fd) < 0) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
}

static void Execvp(const char *file, char *const argv[])
{
    execvp(file, argv);
    perror("execvp");

    exit(EXIT_FAILURE);
}

プログラムは、実行したいアプリケーションと、アプリケーションに渡すすべての引数を引数として使用します。 2つのパイプを作成します。 1つはアプリケーションが書いた内容をキャプチャし、stdoutもう1つはstderrfork()

その後、親プロセスはこれらのパイプからデータを読み取ることができ、poll()これらのファイル記述子でアクティビティを待ちます。プログラムがアプリケーションstdoutまたはストリームから読み取るstderrすべての内容が記録されますstderrstderrプログラムがアプリケーションのストリームから読み取るすべての内容が記録されます。stdoutこれは逆さまに見えますが、リダイレクトを簡素化します。

次のコマンドを使用してCプログラムをコンパイルできます。

$ gcc prog.c -o pipe_wrapper

stdoutそれでは、andの出力を生成することがあるとしましょうstderr

#!/bin/bash
# ex.sh

echo stdout
echo stderr 1>&2

その後、次を実行できます。

# Note that the content written to stderr appears on the console
$ ./pipe_wrapper ./ex.sh 2> allout.log | tee errout.log
stderr
$

# That content written to stderr is captured in errout.log
$ cat errout.log
stderr
$

# And that everything written to either stdout of stderr is captured
# in allout.log
$ cat allout.log
stdout
stderr
$

これが順序を維持することを保証することはできません。アプリケーションがstderrすばやく連続して書き込んでいる場合、プログラムは最初に作成された内容を処理し、次に作成された内容を処理できます。stdoutstdoutstderr

関連情報