我遇到一种情况,我需要按以下方式在 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
我想不出基于 shell 的解决方案,所以我建议这样做。其他人提供了更好的基于 shell 的解决方案。我将把它留在这里以防万一有人觉得有趣,但其他答案更好。
这是一个 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
和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