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

프로그램은 실행하려는 응용 프로그램과 응용 프로그램에 전달하려는 모든 인수를 인수로 사용합니다. 두 개의 파이프를 생성합니다. 하나 는 애플리케이션이 작성하는 내용을 캡처 stdout하고 다른 하나는 stderr.fork()

그런 다음 상위 프로세스는 이러한 파이프에서 데이터를 읽을 수 있으며 poll()이러한 파일 설명자에서 활동을 기다립니다. 프로그램이 애플리케이션 stdout이나 스트림에서 읽는 stderr모든 내용이 기록됩니다 stderr. stderr프로그램이 애플리케이션의 스트림에서 읽는 모든 내용이 기록됩니다. 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

관련 정보