Linux 用 C 实现 shell 并需要帮助处理输入/输出重定向
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11515399/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implementing shell in C and need help handling input/output redirection
提问by Jordan
Round 2
第二轮
After reading some of the answers, my revised code is:
看了一些答案后,我修改后的代码是:
int pid = fork();
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
if (in) { //if '<' char was found in string inputted by user
int fd0 = open(input, O_RDONLY, 0);
dup2(fd0, STDIN_FILENO);
close(fd0);
in = 0;
}
if (out) { //if '>' was found in string inputted by user
int fd1 = creat(output, 0644);
dup2(fd1, STDOUT_FILENO);
close(fd1);
out = 0;
}
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
free(res);
}
It works, but seems the standard output isn't being reconnected or something to that effect. Here is execution:
它有效,但似乎标准输出没有被重新连接或类似的东西。这里是执行:
SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output
'<' and '>' both work, but after they are executed there is no output.
'<' 和 '>' 都可以工作,但执行后没有输出。
Round 1
第1轮
I have been working on a relatively simple shell in C for a while now, but I am having trouble implementing input (<) and output (>) redirection. Help me find the issues in the following code:
我一直在用 C 编写一个相对简单的 shell,但是我在实现输入 (<) 和输出 (>) 重定向时遇到了麻烦。帮我找出以下代码中的问题:
int fd;
int pid = fork();
int current_out;
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_out = dup(0);
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
dup2(current_out, 1);
free(res);
}
I may have some unnecessary material in there because I have been trying different things to get it to work. I am not sure what is going wrong.
我可能有一些不必要的材料,因为我一直在尝试不同的方法来让它发挥作用。我不确定出了什么问题。
采纳答案by Jonathan Leffler
You have way too many file descriptors open after your redirection. Let's dissect the two paragraphs:
重定向后打开的文件描述符太多。让我们剖析一下这两段:
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_in = dup(0); // Fix for symmetry with second paragraph
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
I'm going to be charitable and ignore the fact that you are ignoring errors. However, you will need to error check your system calls.
我将变得慈善并忽略您忽略错误的事实。但是,您需要对系统调用进行错误检查。
In the first paragraph, you open a file and capture the file descriptor (it might well be 3) in the variable fd
. You then duplicate the file descriptor over standard input (STDIN_FILENO
). Note, though, that file descriptor 3 is still open. Then you do a dup(0)
(which, for consistency, should be STDIN_FILENO
), getting another file descriptor, perhaps 4. So you have file descriptors 0, 3 and 4 pointing at the same file (and, indeed, the same open file description — noting that an open file description is different from an open file descriptor). If your intention with current_in
was to preserve the (parent) shell's standard input, you have to do that dup()
before you do the dup2()
that overwrites the output. However, you would be better off not altering the parent shell's file descriptors; it is less overhead than re-duplicating the file descriptors.
在第一段中,您打开一个文件并在变量 中捕获文件描述符(很可能是 3)fd
。然后在标准输入 ( STDIN_FILENO
) 上复制文件描述符。但请注意,文件描述符 3 仍处于打开状态。然后你做了一个dup(0)
(为了一致性,应该是STDIN_FILENO
),得到另一个文件描述符,也许是 4。所以你有文件描述符 0、3 和 4 指向同一个文件(实际上,相同的打开文件描述——注意打开的文件描述不同于打开的文件描述符)。如果您的意图current_in
是保留(父)shell 的标准输入,则必须在执行此操作dup()
之前执行此操作dup2()
覆盖输出。但是,最好不要更改父 shell 的文件描述符;它比重新复制文件描述符的开销更少。
Then you more or less repeat the process in the second paragraph, first overwriting the only record of file descriptor 3 being open with the fd = creat(...)
call but obtaining a new descriptor, perhaps 5, then duplicating that over standard output. You then do a dup(1)
, yielding another file descriptor, perhaps 6.
然后你或多或少地重复第二段中的过程,首先覆盖文件描述符 3 的唯一记录,该记录正在fd = creat(...)
调用中打开,但获得一个新的描述符,也许是 5,然后在标准输出上复制它。然后执行 a dup(1)
,产生另一个文件描述符,可能是 6。
So, you have stdin and stdout of the main shell redirected to the files (and no way of reinstating those to the original values). Your first problem, therefore, is that you are doing the redirection before you fork()
; you should be doing it after the fork()
— though when you get to piping between processes, you will need to create pipes before forking.
因此,您已将主 shell 的 stdin 和 stdout 重定向到文件(并且无法将它们恢复为原始值)。因此,您的第一个问题是您在进行重定向之前fork()
;你应该在之后做fork()
——尽管当你在进程之间进行管道时,你需要在分叉之前创建管道。
Your second problem is that you need to close a plethora of file descriptors, one of which you no longer have a reference for.
你的第二个问题是你需要关闭过多的文件描述符,其中一个你不再有参考。
So, you might need:
所以,你可能需要:
if ((pid = fork()) < 0)
...error...
else if (pid == 0)
{
/* Be childish */
if (in)
{
int fd0 = open(input, O_RDONLY);
dup2(fd0, STDIN_FILENO);
close(fd0);
}
if (out)
{
int fd1 = creat(output , 0644) ;
dup2(fd1, STDOUT_FILENO);
close(fd1);
}
...now the child has stdin coming from the input file,
...stdout going to the output file, and no extra files open.
...it is safe to execute the command to be executed.
execve(cmd[0], cmd, env); // Or your preferred alternative
fprintf(stderr, "Failed to exec %s\n", cmd[0]);
exit(1);
}
else
{
/* Be parental */
...wait for child to die, etc...
}
Before you do any of this, you should ensure that you've already flushed the shell's standard I/O channels, probably by using fflush(0)
, so that if the forked child writes to standard error because of a problem, there is no extraneous duplicated output.
在你做任何这些之前,你应该确保你已经刷新了 shell 的标准 I/O 通道,可能是使用fflush(0)
,这样如果分叉的孩子因为问题而写入标准错误,就不会有多余的重复输出。
Also note that the various open()
calls should be error-checked.
另请注意,open()
应对各种调用进行错误检查。
回答by Greg Inozemtsev
Here's what's happening. After you call fork()
there are two processes executing that are duplicates of the original process. The difference is in the return value of fork()
which is stored in pid
.
这就是正在发生的事情。在您调用之后,fork()
有两个进程在执行,它们是原始进程的副本。不同之处在于其返回值fork()
存储在pid
.
Then both processes (the shell and the child) redirect their stdin and stdout to the same files. I think you were trying to save the previous fd in current_out
, but as Seth Robertson points out, this doesn't currently work, since the wrong file descriptor is being saved. The parent also restores its stdout, but not stdin.
然后两个进程(shell 和子进程)将它们的 stdin 和 stdout 重定向到相同的文件。我认为您试图将之前的 fd 保存在 中current_out
,但是正如 Seth Robertson 指出的那样,这目前不起作用,因为正在保存错误的文件描述符。父级也恢复其标准输出,但不恢复标准输入。
You could fix this bug, but you can do better. You don't actually have to redirect parent's output, just the child's. So simply check pid
first. Then there is also no need to restore any file descriptors.
你可以修复这个错误,但你可以做得更好。您实际上不必重定向父母的输出,只需重定向孩子的。所以只需先检查一下pid
。然后也不需要恢复任何文件描述符。
回答by Shriyansh Agrawal
You have way too many file descriptors open after your redirection. The code which you need is this.
重定向后打开的文件描述符太多。你需要的代码是这个。
if (pid == 0)
{ /* for the child process: */
// function for redirection ( '<' , '>' )
int fd0,fd1,i,in=0,out=0;
char input[64],output[64];
// finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that
for(i=0;argv[i]!='##代码##';i++)
{
if(strcmp(argv[i],"<")==0)
{
argv[i]=NULL;
strcpy(input,argv[i+1]);
in=2;
}
if(strcmp(argv[i],">")==0)
{
argv[i]=NULL;
strcpy(output,argv[i+1]);
out=2;
}
}
//if '<' char was found in string inputted by user
if(in)
{
// fdo is file-descriptor
int fd0;
if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
perror("Couldn't open input file");
exit(0);
}
// dup2() copies content of fdo in input of preceeding file
dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0
close(fd0); // necessary
}
//if '>' char was found in string inputted by user
if (out)
{
int fd1 ;
if ((fd1 = creat(output , 0644)) < 0) {
perror("Couldn't open the output file");
exit(0);
}
dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
close(fd1);
}
execvp(*argv, argv);
perror("execvp");
_exit(1);
// another syntax
/* if (!(execvp(*argv, argv) >= 0)) { // execute the command
printf("*** ERROR: exec failed\n");
exit(1);
*/
}
else if((pid) < 0)
{
printf("fork() failed!\n");
exit(1);
}
else { /* for the parent: */
while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors)
}
}