制作 Linux shell 时的流重定向和管道
我的任务是用 C 创建 Linux shell。目前,我一直致力于实现重定向和管道。到目前为止我拥有的代码如下。 main() 解析用户的输入。如果该命令是内置的,则执行该命令。否则,标记化的输入将传递给execute()(我知道我应该将内置命令拉入它们自己的函数中)。
execute() 的作用是循环遍历数组。如果遇到 <
、>
或 |
,则应采取适当的操作。我想要正确工作的第一件事是管道。不过,我肯定做错了什么,因为我什至无法让它为一根管道工作。例如,示例输入/输出:
/home/ad/Documents> ls -l | grep sh
|: sh: No such file or directory
|
我的想法是让每个方向和管道仅适用于一种情况,然后通过使函数递归,我希望可以在同一命令行中使用多个重定向/管道。例如,我可以执行 program1 <输入1.txt> output1.txt
或 ls -l | grep sh >;输出2.txt
。
我希望有人能指出我在尝试管道时的错误,并可能提供一些关于如何处理用户输入多个重定向/管道的情况的指示。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
void execute(char **argArray){
char **pA = argArray;
int i = 0;
while(*pA != NULL) {
if(strcmp(argArray[i],"<") == 0) {
printf("<\n");
}
else if(strcmp(argArray[i],">") == 0) {
printf(">\n");
}
else if(strcmp(argArray[i],"|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
else {
if(pid == 0){
execvp(argArray[0], argArray);
printf("Command not found.\n");
}
else
wait(NULL);*/
}
*pA++;
i++;
}
}
int main () {
char path[MAX_PATH_LENGTH];
char buf[BUF_LENGTH];
char* strArray[BUF_LENGTH];
/**
* "Welcome" message. When mash is executed, the current working directory
* is displayed followed by >. For example, if user is in /usr/lib/, then
* mash will display :
* /usr/lib/>
**/
getcwd(path, MAX_PATH_LENGTH);
printf("%s> ", path);
fflush(stdout);
/**
* Loop infinitely while waiting for input from user.
* Parse input and display "welcome" message again.
**/
while(1) {
fgets(buf, BUF_LENGTH, stdin);
char *tokenPtr = NULL;
int i = 0;
tokenPtr = strtok(buf, delims);
if(strcmp(tokenPtr, "exit") == 0){
exit(0);
}
else if(strcmp(tokenPtr, "cd") == 0){
tokenPtr = strtok(NULL, delims);
if(chdir(tokenPtr) != 0){
printf("Path not found.\n");
}
getcwd(path, MAX_PATH_LENGTH);
}
else if(strcmp(tokenPtr, "pwd") == 0){
printf("%s\n", path);
}
else {
while(tokenPtr != NULL) {
strArray[i++] = tokenPtr;
tokenPtr = strtok(NULL, delims);
}
execute(strArray);
}
bzero(strArray, sizeof(strArray)); // clears array
printf("%s> ", path);
fflush(stdout);
}
}
I have an assignment to create a Linux shell in C. Currently, I am stuck on implementing redirections and pipes. The code that I have so far is below. The main() parses user's input. If the command is built in, then that command is executed. Otherwise, the tokenized input is passed to execute() (I know that I should probably pull the built-in commands into their own function).
What execute() does is loop through the array. If it encounters <
, >
, or |
it should take appropriate action. The first thing I am trying to get to work correctly is piping. I am definitely doing something wrong, though, because I cannot get it to work for even one pipe. For example, a sample input/output:
/home/ad/Documents> ls -l | grep sh
|: sh: No such file or directory
|
My idea was to get each of the directions and piping work for just one case, and then by making the function recursive I could hopefully use multiple redirections/pipes in the same command line. For example, I could do program1 < input1.txt > output1.txt
or ls -l | grep sh > output2.txt
.
I was hoping that someone can point out my errors in trying to pipe and perhaps offer some pointers in how to approach the case where multiple redirections/pipes are inputted by the user.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
void execute(char **argArray){
char **pA = argArray;
int i = 0;
while(*pA != NULL) {
if(strcmp(argArray[i],"<") == 0) {
printf("<\n");
}
else if(strcmp(argArray[i],">") == 0) {
printf(">\n");
}
else if(strcmp(argArray[i],"|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
else {
if(pid == 0){
execvp(argArray[0], argArray);
printf("Command not found.\n");
}
else
wait(NULL);*/
}
*pA++;
i++;
}
}
int main () {
char path[MAX_PATH_LENGTH];
char buf[BUF_LENGTH];
char* strArray[BUF_LENGTH];
/**
* "Welcome" message. When mash is executed, the current working directory
* is displayed followed by >. For example, if user is in /usr/lib/, then
* mash will display :
* /usr/lib/>
**/
getcwd(path, MAX_PATH_LENGTH);
printf("%s> ", path);
fflush(stdout);
/**
* Loop infinitely while waiting for input from user.
* Parse input and display "welcome" message again.
**/
while(1) {
fgets(buf, BUF_LENGTH, stdin);
char *tokenPtr = NULL;
int i = 0;
tokenPtr = strtok(buf, delims);
if(strcmp(tokenPtr, "exit") == 0){
exit(0);
}
else if(strcmp(tokenPtr, "cd") == 0){
tokenPtr = strtok(NULL, delims);
if(chdir(tokenPtr) != 0){
printf("Path not found.\n");
}
getcwd(path, MAX_PATH_LENGTH);
}
else if(strcmp(tokenPtr, "pwd") == 0){
printf("%s\n", path);
}
else {
while(tokenPtr != NULL) {
strArray[i++] = tokenPtr;
tokenPtr = strtok(NULL, delims);
}
execute(strArray);
}
bzero(strArray, sizeof(strArray)); // clears array
printf("%s> ", path);
fflush(stdout);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
![扫码二维码加入Web技术交流群](/public/img/jiaqun_03.jpg)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
部分问题出在管道处理代码中 - 正如您所怀疑的那样。
第一个
execvp()
可能旨在使用argList
,因为您刚刚在那里复制了一些材料。但是,您复制了i
个字节,而不是i
个字符指针,并且您无法确保管道被zapped 并替换为空指针。请注意,这尚未验证请注意,没有为argList
上没有缓冲区溢出;argList
分配空间;如果你使用它,你应该在执行memcpy()
之前分配内存。或者,更简单的是,您可以不使用副本。由于您处于子进程中,因此您可以简单地用空指针
zap替换argArray[i]
,而不会影响父进程或其他子进程:您也可以请注意,
execvp()
的第二次调用使用了一个看不到的变量pA
;几乎可以肯定它的初始化不正确。作为一个不错的经验法则,您应该这样写:上面的调用不符合这个模式,但是如果您遵循它,您就不会出错。
您还应该在每个 < code>execvp() 以便子进程在执行失败时不会继续。
execvp()
没有成功返回,但execvp()
肯定可以返回。最后,现在,这些对 execvp() 的调用大概应该是您进行递归调用的地方。在尝试处理其他 I/O 重定向之前,您需要先处理管道。请注意,在标准 shell 中,您可以执行以下操作:
这是常规用法,但实际上是允许的。
一件好事 - 您已确保来自
pipe()
的原始文件描述符在所有三个进程(父进程和两个子进程)中都已关闭。这是您避免犯的常见错误;干得好。Part of the problem is in the pipe handling code - as you suspected.
The first
execvp()
was probably intended to useargList
since you've just copied some material there. However, you've copiedi
bytes, noti
character pointers, and you've not ensured that the pipe iszapped andreplaced with a null pointer.Note that this has not verified that there is no buffer overflow onNote that there is no space allocated forargList
;argList
; if you use it, you should allocate the memory before doing thememcpy()
.Alternatively, and more simply, you can do without the copy. Since you're in a child process, you can simply
zapreplaceargArray[i]
with a null pointer without affecting either the parent or the other child process:You might also note that the second invocation of
execvp()
uses a variablepA
which cannot be seen; it is almost certainly incorrectly initialized. As a moderately good rule of thumb, you should write:The invocations above don't conform to this schema, but if you follow it, you won't go far wrong.
You should also have basic error reporting and a
exit(1)
(or possibly_exit(1)
or_Exit(1)
) after eachexecvp()
so that the child does not continue if it fails to execute. There is no successful return fromexecvp()
, butexecvp()
most certainly can return.Finally for now, these calls to
execvp()
should presumably be where you make your recursive call. You need to deal with pipes before trying to deal with other I/O redirection. Note that in a standard shell, you can do:This is aconventional usage, but is actually permitted.
One good thing - you have ensured that the original file descriptors from
pipe()
are closed in all three processes (parent and both children). This is a common mistake which you have avoided making; well done.