有没有办法将 stderr 重定向到在 bash、csh 和 dash 中工作的文件?
Is there a way to redirect stderr to a file that works in bash, csh and dash?
如果我不知道哪个 shell(bash
,我该如何重定向 stderr
(或 stdout
+stderr
)到一个文件csh
, dash
) 正在解释我的命令?
我在 Linux/FreeBSD/OSX 上的 C 代码 运行 需要通过 system()
函数调用外部程序,该函数将使用 /bin/sh
来解释提供的命令行。我想将该外部程序打印的消息捕获到 stderr
并将它们保存到一个文件中。问题是在不同的系统上,/bin/sh
指向不同的 shell,它们具有将 stderr
流重定向到文件的不同语法。
我发现最接近的是 bash 实际上理解 csh
风格的语法,用于将 stderr
+stdout
重定向到一个文件:
some_program >& output.txt
但是 dash
,这是 Ubuntu 上的默认 shell(即非常常见),不理解这种语法。
是否有一种 stderr
重定向的语法可以被所有常见的 shell 正确解释?或者,有没有办法告诉 system()
(或其他一些类似的 C 函数?)使用 /usr/bin/env bash
而不是 /bin/sh
来解释提供的命令行?
在任何 POSIX-like 系统上,您可以使用
system("some_program > output.txt 2>&1");
这是因为 POSIX system
是 equivalent to calling sh
, and POSIX sh
supports this kind of redirection。这与打开系统终端的用户是否会看到 Csh 提示无关。
How do I redirect stderr (or stdout+stderr) to a file if I don't know which shell (bash, csh, dash) is interpreting my command?
你不知道。 Bourne-family shells 和 csh-family shells 具有不同的、不兼容的重定向 stderr 语法。事实上,csh
和 tcsh
根本没有仅重定向 stderr 的语法——它们只能将它与 stdout 一起重定向。
如果您真的可以在任何 shell 中,那么您对做任何事情都非常满意。人们可以想象一个晦涩、深奥的 shell 和完全不兼容的语法。就此而言,即使是标准 shell 的不寻常配置也可能使您出错——例如,如果 IFS
变量在 Bourne-family shell 中设置为不寻常的值,那么您将无法执行任何未考虑到这一点的命令。
如果你能指望至少执行简单的命令,那么你可以在未知命令中执行已知的 shell 来处理你的命令,但对于似乎感兴趣。
Alternatively, is there a way to tell system() (or some other similar
C function?) to use /usr/bin/env bash instead of /bin/sh to interpret
the supplied command line?
不在 POSIX-conforming 系统上。 POSIX 明确指定 system()
函数通过使用 /bin/sh -c [the_command]
来执行命令。但这 不应该 是值得担心的事情,因为 /bin/sh
应该 是一个合格的 POSIX shell,或者至少非常接近一个。绝对应该是 Bourne-family shell,bash
和 dash
都是,但 tcsh
绝对不是。
在 POSIX shell 中重定向标准错误流的方法是使用 2>
重定向运算符(这是适用于任意 文件描述符)。无论 shell /bin/sh
实际上是什么,都应该识别该语法,特别是 bash
和 dash
都可以:
some_program 2> output.txt
您有一个错误的假设,即 /bin/sh
可以是 "alternate" shell 之类的 csh
,它与标准 shell 语法不兼容。如果您有这样的系统设置,它将无法使用;没有 shell 脚本可以工作。几乎所有现代系统都试图至少在表面上符合 POSIX 标准,其中 sh
命令处理 POSIX 中指定的 Shell 命令语言,这大致是相当于历史上的 Bourne shell 和 bash
、dash
、ash
等(通常安装为 /bin/sh
的 shell)都 99.9% 兼容。
你完全可以忽略csh
之类的。它们从未安装为 sh
,只有那些真正想要使用它们的人,或者因为一些邪恶的系统管理员设置登录 shell 默认而被困在使用它们作为他们的互动 shell 的人方式,永远不必关心他们。
我认为,还有另一种可能性值得一提:您可以在调用 system()
之前在 c-code 中打开要在 stderr 上重定向的文件。可以先dup()
原来的stderr,然后再恢复。
fflush(stderr); // Flush pending output
int saved_stderr = dup(fileno(stderr));
int fd = open("output.txt", O_RDWR|O_CREAT|O_TRUNC, 0600);
dup2(fd, fileno(stderr));
close(fd);
system("some_program");
dup2(saved_stderr, fileno(stderr));
close(saved_stderr);
这应该根据需要执行输出重定向。
如果你不知道 shell...当然你不知道如何从它重定向,尽管你可以看到 $SHELL
的值有,并因此采取行动:
char *shell = getenv("SHELL");
if (*shell) { /* no SHELL variable defined */
/* ... */
} else if (!strcmp(shell, "/bin/sh")) { /* bourne shell */
/* ... */
} /* ... more shells */
不管你在问题中说了什么,重命名 /bin/sh
以使用另一个 shell 是很不寻常的,因为 shell 脚本使用依赖于它的语法。我知道的唯一情况是 bash(1)
,我只在 Linux 中看到过这种情况(值得注意的是,solaris 的最新版本),但是 bash(1)
的语法是语法的超集sh(1)
,使 运行 shell 脚本成为可能 sh(1)
。例如,将 /bin/sh
重命名为 perl
会使您的系统可能完全无法使用,因为许多系统工具依赖于 /bin/sh
才能与 bourne 兼容 shell.
顺便说一句,system(3)
库函数总是调用sh(1)
作为命令解释器,所以使用它应该没有问题,但是没有捕获输出并处理它的解决方案通过 parent 过程(实际上,parent 过程是 sh(1)
即 system(3)
fork(2)
s)
您可以做的另一件事是 popen(3)
一个进程。此调用为您提供了一个指向进程管道的 FILE
指针。如果你 popen(3)
它用于写入,你可以打开它的输入,如果你想要或读取它的输出,你可以打开它的输出。查看手册以获取详细信息,因为我现在不知道它是仅重定向其标准输出还是还重定向标准错误(我认为仅重定向标准输出,原因如下所述,并且仅当您 popen(3)
它带有 "r"
标志。
FILE *f_in = popen("ps aux", "r");
/* read standard output of 'ps aux' command. */
pclose(f_in); /* closes the descriptor and waits for the child to finish */
您可以做的另一件事是在 fork(2)
调用 child 之后和 exec(2)
调用之前重定向自己(这样您可以决定是否只需要 stdout
或者如果你还想 stderr
重定向回你):
int fd[2];
int res = pipe(fd);
if (res < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
if ((res = fork()) < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (res == 0) { /* child process */
dup2(fd[1], 1); /* redirect pipe to stdout */
dup2(fd[1], 2); /* redirect pipe also to stderr */
close(fd[1]); close(fd[0]); /* we don't need these */
execvp(program, argv);
perror("execvp");
exit(EXIT_FAILURE);
} else { /* parent process */
close(fd[1]); /* we are not going to write in the pipe */
FILE *f_in = fdopen(fd[0]);
/* read standard output and standard error from program from f_in FILE descriptor */
fclose(f_in);
wait(NULL); /* wait for child to finish */
}
您可以看到一个完整的示例(不是读取标准错误,但很容易添加 --- 您只需添加上面的第二个 dup2() 调用)here。该程序重复执行您在命令行上传递给它的命令。它需要访问子进程的输出以计算行数,因为在两次调用之间,程序增加与程序输出一样多的行数,以使下一次调用与上次调用的输出重叠。大家可以试玩,随意修改。
注意
在您的示例重定向中,当您使用 >&
时,您需要在 & 符号后添加一个数字,以表明您正在 dup()
ing 哪个描述符。由于 >
之前的数字是可选的,因此 &
之后的数字是必填的。所以,如果你还没有使用它,准备接收一个错误(如果你正在重定向,你可能看不到 stderr
) 有两个单独的输出描述符的想法是允许你重定向 stdout
同时,保留一个放置错误信息的渠道。
如果我不知道哪个 shell(bash
,我该如何重定向 stderr
(或 stdout
+stderr
)到一个文件csh
, dash
) 正在解释我的命令?
我在 Linux/FreeBSD/OSX 上的 C 代码 运行 需要通过 system()
函数调用外部程序,该函数将使用 /bin/sh
来解释提供的命令行。我想将该外部程序打印的消息捕获到 stderr
并将它们保存到一个文件中。问题是在不同的系统上,/bin/sh
指向不同的 shell,它们具有将 stderr
流重定向到文件的不同语法。
我发现最接近的是 bash 实际上理解 csh
风格的语法,用于将 stderr
+stdout
重定向到一个文件:
some_program >& output.txt
但是 dash
,这是 Ubuntu 上的默认 shell(即非常常见),不理解这种语法。
是否有一种 stderr
重定向的语法可以被所有常见的 shell 正确解释?或者,有没有办法告诉 system()
(或其他一些类似的 C 函数?)使用 /usr/bin/env bash
而不是 /bin/sh
来解释提供的命令行?
在任何 POSIX-like 系统上,您可以使用
system("some_program > output.txt 2>&1");
这是因为 POSIX system
是 equivalent to calling sh
, and POSIX sh
supports this kind of redirection。这与打开系统终端的用户是否会看到 Csh 提示无关。
How do I redirect stderr (or stdout+stderr) to a file if I don't know which shell (bash, csh, dash) is interpreting my command?
你不知道。 Bourne-family shells 和 csh-family shells 具有不同的、不兼容的重定向 stderr 语法。事实上,csh
和 tcsh
根本没有仅重定向 stderr 的语法——它们只能将它与 stdout 一起重定向。
如果您真的可以在任何 shell 中,那么您对做任何事情都非常满意。人们可以想象一个晦涩、深奥的 shell 和完全不兼容的语法。就此而言,即使是标准 shell 的不寻常配置也可能使您出错——例如,如果 IFS
变量在 Bourne-family shell 中设置为不寻常的值,那么您将无法执行任何未考虑到这一点的命令。
如果你能指望至少执行简单的命令,那么你可以在未知命令中执行已知的 shell 来处理你的命令,但对于似乎感兴趣。
Alternatively, is there a way to tell system() (or some other similar C function?) to use /usr/bin/env bash instead of /bin/sh to interpret the supplied command line?
不在 POSIX-conforming 系统上。 POSIX 明确指定 system()
函数通过使用 /bin/sh -c [the_command]
来执行命令。但这 不应该 是值得担心的事情,因为 /bin/sh
应该 是一个合格的 POSIX shell,或者至少非常接近一个。绝对应该是 Bourne-family shell,bash
和 dash
都是,但 tcsh
绝对不是。
在 POSIX shell 中重定向标准错误流的方法是使用 2>
重定向运算符(这是适用于任意 文件描述符)。无论 shell /bin/sh
实际上是什么,都应该识别该语法,特别是 bash
和 dash
都可以:
some_program 2> output.txt
您有一个错误的假设,即 /bin/sh
可以是 "alternate" shell 之类的 csh
,它与标准 shell 语法不兼容。如果您有这样的系统设置,它将无法使用;没有 shell 脚本可以工作。几乎所有现代系统都试图至少在表面上符合 POSIX 标准,其中 sh
命令处理 POSIX 中指定的 Shell 命令语言,这大致是相当于历史上的 Bourne shell 和 bash
、dash
、ash
等(通常安装为 /bin/sh
的 shell)都 99.9% 兼容。
你完全可以忽略csh
之类的。它们从未安装为 sh
,只有那些真正想要使用它们的人,或者因为一些邪恶的系统管理员设置登录 shell 默认而被困在使用它们作为他们的互动 shell 的人方式,永远不必关心他们。
我认为,还有另一种可能性值得一提:您可以在调用 system()
之前在 c-code 中打开要在 stderr 上重定向的文件。可以先dup()
原来的stderr,然后再恢复。
fflush(stderr); // Flush pending output
int saved_stderr = dup(fileno(stderr));
int fd = open("output.txt", O_RDWR|O_CREAT|O_TRUNC, 0600);
dup2(fd, fileno(stderr));
close(fd);
system("some_program");
dup2(saved_stderr, fileno(stderr));
close(saved_stderr);
这应该根据需要执行输出重定向。
如果你不知道 shell...当然你不知道如何从它重定向,尽管你可以看到 $SHELL
的值有,并因此采取行动:
char *shell = getenv("SHELL");
if (*shell) { /* no SHELL variable defined */
/* ... */
} else if (!strcmp(shell, "/bin/sh")) { /* bourne shell */
/* ... */
} /* ... more shells */
不管你在问题中说了什么,重命名 /bin/sh
以使用另一个 shell 是很不寻常的,因为 shell 脚本使用依赖于它的语法。我知道的唯一情况是 bash(1)
,我只在 Linux 中看到过这种情况(值得注意的是,solaris 的最新版本),但是 bash(1)
的语法是语法的超集sh(1)
,使 运行 shell 脚本成为可能 sh(1)
。例如,将 /bin/sh
重命名为 perl
会使您的系统可能完全无法使用,因为许多系统工具依赖于 /bin/sh
才能与 bourne 兼容 shell.
顺便说一句,system(3)
库函数总是调用sh(1)
作为命令解释器,所以使用它应该没有问题,但是没有捕获输出并处理它的解决方案通过 parent 过程(实际上,parent 过程是 sh(1)
即 system(3)
fork(2)
s)
您可以做的另一件事是 popen(3)
一个进程。此调用为您提供了一个指向进程管道的 FILE
指针。如果你 popen(3)
它用于写入,你可以打开它的输入,如果你想要或读取它的输出,你可以打开它的输出。查看手册以获取详细信息,因为我现在不知道它是仅重定向其标准输出还是还重定向标准错误(我认为仅重定向标准输出,原因如下所述,并且仅当您 popen(3)
它带有 "r"
标志。
FILE *f_in = popen("ps aux", "r");
/* read standard output of 'ps aux' command. */
pclose(f_in); /* closes the descriptor and waits for the child to finish */
您可以做的另一件事是在 fork(2)
调用 child 之后和 exec(2)
调用之前重定向自己(这样您可以决定是否只需要 stdout
或者如果你还想 stderr
重定向回你):
int fd[2];
int res = pipe(fd);
if (res < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
if ((res = fork()) < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (res == 0) { /* child process */
dup2(fd[1], 1); /* redirect pipe to stdout */
dup2(fd[1], 2); /* redirect pipe also to stderr */
close(fd[1]); close(fd[0]); /* we don't need these */
execvp(program, argv);
perror("execvp");
exit(EXIT_FAILURE);
} else { /* parent process */
close(fd[1]); /* we are not going to write in the pipe */
FILE *f_in = fdopen(fd[0]);
/* read standard output and standard error from program from f_in FILE descriptor */
fclose(f_in);
wait(NULL); /* wait for child to finish */
}
您可以看到一个完整的示例(不是读取标准错误,但很容易添加 --- 您只需添加上面的第二个 dup2() 调用)here。该程序重复执行您在命令行上传递给它的命令。它需要访问子进程的输出以计算行数,因为在两次调用之间,程序增加与程序输出一样多的行数,以使下一次调用与上次调用的输出重叠。大家可以试玩,随意修改。
注意
在您的示例重定向中,当您使用 >&
时,您需要在 & 符号后添加一个数字,以表明您正在 dup()
ing 哪个描述符。由于 >
之前的数字是可选的,因此 &
之后的数字是必填的。所以,如果你还没有使用它,准备接收一个错误(如果你正在重定向,你可能看不到 stderr
) 有两个单独的输出描述符的想法是允许你重定向 stdout
同时,保留一个放置错误信息的渠道。