流重定向和管道做一个Linux shell时 [英] Stream redirection and pipes when making a Linux shell

查看:333
本文介绍了流重定向和管道做一个Linux shell时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个分配创建C.目前Linux外壳,我被困在实施重定向和管道。在code是我到目前为止如下。主()分析用户的输入。如果该命令是内置的,则该命令被执行。否则,符号化的输入被传递到执行()(我知道,我也许应该拉内置命令到自己的功能)。

什么的execute()的作用是通过数组循环。如果遇到< > | 应当采取适当的行动。我想获得正常工作的第一件事就是管道。我肯定做错了什么,不过,因为我不能让它甚至一个管工作。例如,一个样本输入/输出:

  /家庭/广告/文件> ls -l命令| grep的SH
|:SH:没有这样的文件或目录
|

我的想法是让每一个方向和管道的工作只是一个个案,然后通过该函数的递归我希望在同一个命令行中使用多个重定向/管道。例如,我可以做程序1< input1.txt> output1.txt 的ls -l | grep的SH> output2.txt

我希望有人能在试图管指出我的错误,也许在提供如何处理,其中多个重定向/管道由用户输入的情况下,一些三分球。

 的#include<&stdio.h中GT;
#包括LT&;&unistd.h中GT;
#包括LT&;&fcntl.h GT;
#包括LT&;&string.h中GT;
#包括LT&;&stdlib.h中GT;INT MAX_PATH_LENGTH = 1024; //最大路径长度来显示。
INT BUF_LENGTH = 1024; //缓冲区的长度来存储用户输入
字符* delims =\\ n; //为记号化处理用户输入分隔符。
const int的PIPE_READ = 0;
const int的PIPE_WRITE = 1;无效的execute(字符**必要参数){  焦炭** PA =必要参数;
  INT I = 0;
  而(* pA的!= NULL){
    如果(的strcmp(必要参数[I]中,&下;)== 0){
        的printf(< \\ n);
    }
    否则如果(的strcmp(必要参数[I]中,>中)== 0){
        的printf(> \\ n);
    }
    否则如果(的strcmp(必要参数[I]中,|)== 0){
        INT FDS [2];
        管(FDS);
        将为pid_t PID;
        如果((PID =叉())== 0){
            dup2(FDS [PIPE_WRITE],1);
            关闭(FDS [PIPE_READ]);
            关闭(FDS [PIPE_WRITE]);
            焦炭** ARGLIST;
            的memcpy(ARGLIST,必要参数,I);
            execvp(必要参数[0],必要参数);
        }
        如果((PID =叉())== 0){
            dup2(FDS [PIPE_READ],0);
            关闭(FDS [PIPE_READ]);
            关闭(FDS [PIPE_WRITE]);
            execvp(必要参数[I + 1],PA);
        }
        关闭(FDS [PIPE_READ]);
        关闭(FDS [PIPE_WRITE]);
        等待(NULL);
        等待(NULL);
        的printf(| \\ n);
    }
    其他{
        如果(PID == 0){
            execvp(必要参数[0],必要参数);
            的printf(命令未找到\\ n);
        }
        其他
            等待(NULL); * /
    }
    * PA ++;
    我++;
  }
}诠释主(){  焦炭路径[MAX_PATH_LENGTH]
  烧焦的buf [BUF_LENGTH]
  字符* strArray [BUF_LENGTH]
  / **
   *欢迎消息。当执行捣烂,当前的工作目录
   显示*随后取代。例如,如果用户在/ usr / lib目录/,然后
   *捣烂会显示:
   * / usr / lib目录/>
   ** /
  GETCWD(路径,MAX_PATH_LENGTH);
  的printf(%S>中,路径);
  fflush(标准输出);  / **
   *无限循环而来自用户的等待输入。
   *解析输入,再次显示欢迎的消息。
   ** /
  而(1){
    与fgets(BUF,BUF_LENGTH,标准输入);
    字符* tokenPtr = NULL;
    INT I = 0;
    tokenPtr = strtok的(BUF,delims);    如果(STRCMP(tokenPtr,退出)== 0){        出口(0);
    }
    否则,如果(STRCMP(tokenPtr,CD)== 0){
        tokenPtr =的strtok(NULL,delims);
        如果(CHDIR(tokenPtr)!= 0){
            的printf(路径未找到\\ n);
        }
        GETCWD(路径,MAX_PATH_LENGTH);
    }
    否则,如果(STRCMP(tokenPtr,PWD)== 0){
        的printf(%S \\ n,路径);
    }
    其他{
        而(tokenPtr!= NULL){
            strArray [我++] = tokenPtr;
            tokenPtr =的strtok(NULL,delims);
        }
        执行(strArray);
    }    bzero(strArray,sizeof的(strArray)); //清除数组
    的printf(%S>中,路径);
    fflush(标准输出);
  }}


解决方案

问题的部分原因是管道处理code - 当你怀疑

 否则如果(STRCMP(必要参数[I],|)== 0){
    INT FDS [2];
    管(FDS);
    将为pid_t PID;
    如果((PID =叉())== 0){
        dup2(FDS [PIPE_WRITE],1);
        关闭(FDS [PIPE_READ]);
        关闭(FDS [PIPE_WRITE]);
        焦炭** ARGLIST;
        的memcpy(ARGLIST,必要参数,I);
        execvp(必要参数[0],必要参数);
    }
    如果((PID =叉())== 0){
        dup2(FDS [PIPE_READ],0);
        关闭(FDS [PIPE_READ]);
        关闭(FDS [PIPE_WRITE]);
        execvp(必要参数[I + 1],PA);
    }
    关闭(FDS [PIPE_READ]);
    关闭(FDS [PIPE_WRITE]);
    等待(NULL);
    等待(NULL);
    的printf(| \\ n);
}

第一个 execvp(),可能是为了使用 ARGLIST 因为你只是复制了一些材料在那里。但是,您复制 I 字节,而不是 I 字符指针,你不能确保该管死机,用一个空指针代替。

 的memcpy(ARGLIST,必要参数,我*的sizeof(字符*));
ARGLIST [I] = 0;
execvp(ARGLIST [0],ARGLIST);

请注意,这并没有证实没有对 ARGLIST 没有缓冲区溢出; 注意,没有分配给<$ C $空间C> ARGLIST ;如果你使用它,你应该做的的memcpy之前​​分配内存()

另外,更简单地说,你可以不拷贝做。既然你是在一个子进程,你可以简单地 ZAP 用一个空指针替换必要参数[I] 而不影响父母或其他子进程:

 必要参数[I] = 0;
execvp(必要参数[0],必要参数);

您可能也注意到,的第二次调用execvp()使用一个变量公共广播这不能被看到;它几乎可以肯定是不正确初始化。作为一个经验适度良好的规则,你应该写:

  execvp(数组[N],和放大器;数组[n]);

上面的调用不符合这个模式,但如果你遵循它,你就不会出大错。

您也应该有基本的错误报告和退出(1)(或可能 _exit(1) _Exit(1))的每个 execvp后(),这样,如果不履行的孩子不会继续。有没有从成功返回 execvp(),但 execvp()肯定可以恢复。

最后就目前而言,这些调用 execvp()应该presumably情况下,您让您的递归调用。你需要设法处理其他I / O重定向之前处理管道。请注意,在一个标准的shell,你可以这样做:

 &GT;输出&LT;输入命令-opts ARG1 ARG2

这是aconventional的用法,但实际上是允许的。

一件好事 - 你必须确保从原来的文件描述符管()在所有三个过程(父母和两个孩子)关闭。这是你做可以避免一个常见的​​错误;做得很好。

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);
  }

}

解决方案

Part of the problem is in the pipe handling code - as you suspected.

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");
}

The first execvp() was probably intended to use argList since you've just copied some material there. However, you've copied i bytes, not i character pointers, and you've not ensured that the pipe is zapped and replaced with a null pointer.

memcpy(argList, argArray, i * sizeof(char *));
argList[i] = 0;
execvp(argList[0], argList);

Note that this has not verified that there is no buffer overflow on argList; Note that there is no space allocated for argList; if you use it, you should allocate the memory before doing the memcpy().

Alternatively, and more simply, you can do without the copy. Since you're in a child process, you can simply zap replace argArray[i] with a null pointer without affecting either the parent or the other child process:

argArray[i] = 0;
execvp(argArray[0], argArray);

You might also note that the second invocation of execvp() uses a variable pA which cannot be seen; it is almost certainly incorrectly initialized. As a moderately good rule of thumb, you should write:

execvp(array[n], &array[n]);

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 each execvp() so that the child does not continue if it fails to execute. There is no successful return from execvp(), but execvp() 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:

> output < input command -opts arg1 arg2

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.

这篇关于流重定向和管道做一个Linux shell时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆