带有 fork 和 exec 的普通 shell
如果你有一个普通的 shell,你正在使用 fork() 和 execvp() 或其亲属之一,那么你应该分叉子进程并在子进程中处理 I/O 重定向(使用 open(),
dup2() 或 dup(),和
close()),保持父级的 I/O 不变。
假设您已将命令解析为:
char *argv[] = { "simple-command", "arg1", "arg2", 0 };
并将标准输出文件名改成:
char *file1 = "filename";
然后:
if ((pid = fork()) < 0)
...report error...
else if (pid == 0)
{
int fd = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
...report error and exit...
dup2(fd, STDOUT_FILENO);
close(fd);
execvp(argv[0], argv);
...report error and exit...
}
else
{
/* Do whatever the parent needs to do */
}
请注意,父级根本不需要更改其标准输出。
你也可以使用序列:
close(STDOUT_FILENO);
int fd = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644); // Or 0666
if (fd < 0)
...report error and exit...
if (fd != STDOUT_FILENO)
...standard input must have been closed too...
不使用 fork 和 exec 重定向内置命令
除了在错误的过程中重定向之外,使用
freopen() 或者 fdopen() 并重新打开 /dev/stdout 将不起作用。 /dev/stdout 指向的文件会随着程序的运行而变化,并且随着主程序更改其文件描述符 1(标准输出)指向的内容而打开或关闭文件。
如果您确实需要将主 shell 的标准输出重定向为内置,其中不使用 fork() 和 exec()-family 函数,那么您需要执行以下等效操作:
fflush(stdout); // Safety precaution
int fd1 = open(file1, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd1 < 0)
...report error and do not continue...
int fd2 = dup(STDOUT_FILENO);
if (fd2 < 0)
...report error and do not continue...
if (dup2(fd2, STDOUT_FILENO) < 0)
...report error and do not continue...
close(fd1); // check for error?
...do whatever operations need standard output redirected...
fflush(stdout); // Safety precaution
if (dup2(fd1, STDOUT_FILENO) < 0) // Bug fixed!
...report error and do not continue...
close(fd2);
工作代码
对不起,我没有完全理解这段代码(C 的新手)。能举个完整的例子吗?
好的。对于我第一次编码的内置命令,我已经放弃了指向函数的指针的一般性;我的结论是它可能会让你更加困惑。我拒绝没有“打印错误”功能(尽管我经常加强它以报告errno 和strerror() 以及打印基本错误消息)。
我将源文件称为simpleshell.c 和程序,因此称为simpleshell,但这夸大了它的作用。
#include <stdarg.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
static int err_report(const char *fmt, ...);
static int redirect_echo_to_file(char **argv, char *file)
{
/* Connect standard output to given file */
fflush(stdout);
int fd1 = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd1 < 0)
return err_report("Failed to open %s for writing\n", file);
int fd2 = dup(STDOUT_FILENO);
if (fd2 < 0)
return err_report("Failed to duplicate standard output\n");
if (dup2(fd1, STDOUT_FILENO) < 0)
return err_report("Failed to duplicate %s to standard output\n", file);
close(fd1);
/* Write to standard output */
char *pad = "";
while (*argv != 0)
{
printf("%s%s", pad, *argv++);
pad = " ";
}
putchar('\n');
/* Reconnect original standard output */
fflush(stdout);
if (dup2(fd2, STDOUT_FILENO) < 0)
return err_report("Failed to reinstate standard output\n");
close(fd2);
return 0;
}
int main(void)
{
char *file1 = "file.01";
char *file2 = "file.02";
char *arguments[] = { "Hello", "world!", "This", "is your", "echo", "speaking." };
printf("This is the surrogate shell at work\n");
printf("Echo the same message to two different files (%s and %s)\n", file1, file2);
if (redirect_echo_to_file(arguments, file1) != 0 ||
redirect_echo_to_file(arguments, file2) != 0)
return -1;
printf("Regular shell output on standard output.\n");
printf("Please check %s and %s for a special message.\n", file1, file2);
return 0;
}
static int err_report(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
return -1;
}
样本输出
$ ./simpleshell
This is the surrogate shell at work
Echo the same message to two different files (file.01 and file.02)
Regular shell output on standard output.
Please check file.01 and file.02 for a special message.
$ cat file.01
Hello world! This is your echo speaking.
$ cat file.02
Hello world! This is your echo speaking.
$