次のように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);
}
プログラムは、実行したいアプリケーションと、アプリケーションに渡すすべての引数を引数として使用します。 2つのパイプを作成します。 1つはアプリケーションが書いた内容をキャプチャし、stdout
もう1つは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