다음과 같이 bash 스크립트에서 출력 리디렉션을 수행해야 하는 상황이 있습니다.
- 콘솔에는 오류 메시지만 표시되어야 합니다.
- 오류 메시지는 errout.log와 같은 특정 파일에도 기록되어야 합니다.
- 표준 출력과 오류 메시지는 모두 allout.log와 같은 파일에 기록되어야 합니다.
- 그렇게 중요하지는 않고 가지고 있으면 좋지만 아마도 많은 노력을 들여 달성할 수 있을 것입니다. 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
빠르게 연속해서 쓰고 쓰는 경우 프로그램은 작성된 내용을 먼저 처리한 다음 작성된 내용을 처리할 수 있습니다.stdout
stdout
stderr