有没有一种方法可以将stderr重定向到可以在bash,csh和dash中工作的文件?

时间:2019-07-09 19:37:03

标签: c bash csh

如果我不知道哪个外壳程序(stderrstdout,如何将stderr(或bash + 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来解释提供的命令行?

5 个答案:

答案 0 :(得分:6)

您有一个错误的假设,即/bin/sh可能是与csh类似的“替代” shell,它与标准shell语法不兼容。如果您具有这样的系统设置,它将无法使用;没有shell脚本会起作用。几乎所有现代系统都至少在表面上尝试遵循POSIX标准,其中sh命令处理POSIX中指定的Shell命令语言,该语言大致等效于历史上的Bourne shell,并且{{1} },bashdash等(通常安装为ash的外壳)都兼容99.9%。

您可以完全忽略/bin/sh等。它们永远不会安装为csh,只有真正想要使用它们的人,或者因为使用一些邪恶的sysadmin设置登录shell默认方式而被卡住作为交互式shell的人,才需要关心它们

答案 1 :(得分:4)

  

如果我不知道哪个shell(bash,csh,破折号)正在解释我的命令,如何将stderr(或stdout + stderr)重定向到文件?

你不知道。 Bourne系列外壳程序和csh系列外壳程序具有不同的,不兼容的语法来重定向stderr。实际上,cshtcsh根本没有仅重定向stderr的语法-它们只能将其与stdout一起重定向。

如果您真的可以装在任何外壳中,那么您在做任何事情上都非常费劲。可以想象一个语法晦涩难懂,语法完全不兼容的外壳。因此,即使是标准外壳的异常配置也可能使您绊倒-例如,如果IFS变量在伯恩家族外壳中设置为异常值,那么您将很难执行任何命令没有考虑到这一点。

如果您可以指望至少执行简单的命令,那么您可以在未知的外壳中执行一个已知的shell来处理您的命令,但是对于您似乎感兴趣的情况,这不是必需的。

  

或者,有一种方法可以告诉system()(或其他一些类似的方法)   C函数?)使用/ usr / bin / env bash代替/ bin / sh进行解释   提供的命令行?

不在符合POSIX的系统上。 POSIX明确指定system()函数通过使用/bin/sh -c [the_command]执行命令。但这不必担心,因为/bin/sh 应该是符合要求的POSIX外壳,或者至少非常接近一个外壳。绝对应该是bashdash都是的伯恩族外壳,但是tcsh绝对不是。

在POSIX shell中重定向标准错误流的方法是使用2>重定向操作符(这是适用于 any 文件描述符的更通用的重定向功能的特例。 )。实际上,无论什么外壳/bin/sh都应该识别该语法,尤其是bashdash都可以做到:

some_program 2> output.txt

答案 2 :(得分:3)

在任何类似POSIX的系统上,您都可以使用

system("some_program > output.txt 2>&1");

这是因为POSIX systemequivalent to calling sh,而POSIX sh supports this kind of redirection。这与用户在系统上打开终端是否会看到Csh提示无关。

答案 3 :(得分:1)

我认为,还有另一种可能性值得一提:在调用system()之前,可以在c代码中的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);

这应该根据需要执行输出重定向。

答案 4 :(得分:1)

如果您不知道该外壳……。当然,您不知道如何从该外壳重定向,尽管您可以看到$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)的语法是{{1 }},从而可以运行为sh(1)创建的shell脚本。例如,将sh(1)重命名为/bin/sh可能会使您的系统完全无法使用,因为许多系统工具依赖perl来成为与bourne兼容的shell。

顺便说一下,/bin/sh库函数始终将system(3)用作命令解释器,因此使用它应该没有问题,但是没有解决方案来捕获输出并由处理程序处理。父进程(实际上,父进程是sh(1) sh(1)的{​​{1}})

您可以做的另一件事是system(3)一个过程。该调用为您提供了fork(2)指向进程管道的指针。如果您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)还是想要还exec(2)重定向回了您):

stdout

您可以看到一个完整的示例(不读取标准错误,但添加起来很容易---您只需从上方添加第二个dup2()调用)即可here。该程序重复执行在命令行上传递给它的命令。它需要访问子流程的输出以对行进行计数,因为在两次调用之间,程序将增加与程序输出一样多的行,以使下一次调用与上次调用的输出重叠。您可以尝试玩,根据自己的喜好进行修改。

注意

在示例重定向中,当您使用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 */ } 使用哪个描述符。由于>&之前的数字是可选的,因此dup()之后的数字是强制性的。因此,如果您没有使用它,请准备接收错误(可能不会看到是否正在重定向>)具有两个单独的输出描述符的想法是允许您重定向{{1} },并同时保留用于放置错误消息的渠道。

相关问题