Analog shell program

Claim:
  1. It can be identified >, <the input and output redirection.
  2. You can identify pipeline operation.
  3. Support for multiple pipes: for example cat | cat | cat | cat.
  4. Support mixed pipes and redirection.
  5. Problem solving pipeline input and output redirection and pipes and output redirection file redirection coexist.

analysis:
  • Simple instructions

        Such command no redirects, no pipeline, its implementation should be the main process creates a child process, the command string assembled into an array of strings and then add a call to execthe function can be.

  • With a redirection instruction

方案一:

        > 和 <Directly as command line arguments to execthe function. Tested, execand can not resolve the redirection symbol .

方案二:

         Then manually open the file after dup2the.

  • With the instruction pipeline

         The instruction from the pipe symbol split into multiple instructions, each instruction given to a child thread, and have a pipeline of two separate child process to get a read end of a pipe, a process to obtain the write end, and the end of the writing process standard output is redirected to the write end of file descriptors. As shown below:

achieve
/**
 *  完成一个模拟shell的程序。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


char commands[1024];            /* 主进程读入的一个整的指令*/
char parts[100][100][1024];     /* 第一维度表示子进程, 第二维度表示该进程指令参数, 第三个维度为参数为具体的值*/
char *vector[100][100];         /* 第一维度表示子进程, 每个进程的vector*/
char input_file[100][1024];     /* 第一维度表示子进程, 输入重定向的文件*/
char output_file[100][1024];    /* 第一维度表示子进程, 输出重定向的文件*/
int input_flag[100] = {0};      /* 第一维度表示子进程, 输入重定向标记*/
char output_flag[100] = {0};    /* 第一维度表示子进程, 输出重定向标记*/
int  pipe_no[100][2];           /* 两个进程共享一对管道的文件描述符*/
char child_command[1024];       /* 临时存放一个子进程的指令*/
int child_process_cnt = 0;      /* 子进程的数量*/

/**
 * 读入指令, 并对指令进行特殊处理去掉结尾的\n
 */
char * readCommand()
{
    char *ret = fgets(commands, 1024, stdin);
    if(ret)
    {
        char * t = strchr(commands, '\n');
        *t = '\0';
    }
    return ret;
}

/**
 * 将参数列表进行解析, 使用strsep函数即可。
 * 将解析结果形成一个vector, 提供给execv函数使用。
 * 解析正确返回0, 解析失败返回-1。
 */
int parse_command()
{
    // 先将大的指令差分成子进程指令
    char *bbuf = commands;
    char *pp = strsep(&bbuf, "|");
    int pro_no = 0;
    while(pp)
    {
        strcpy(child_command, pp);
#ifdef DEBUG
        printf("-%s-\n", child_command);
#endif

        char *buf = child_command;
        char *p = NULL;
        p = strsep(&buf, " ");
        int index = 0, i = 0;
        // 将参数解析的到part当中
        while(p)
        {
            // 可能产生空串的情况
            if(strcmp(p, "") == 0)
            {
                p = strsep(&buf, " ");
                continue;
            }
#ifdef DEBUG
            printf("#%s#\n", p); 
#endif
            // 遇见输入重定向标记, 则获取输出文件名   
            if(strcmp(p, "<") == 0) 
            {
                input_flag[pro_no] = 1;
                p = strsep(&buf, " ");
                if(!p || strcmp(p, "") == 0) return -1;
                if(strcmp(p, "<") == 0)
                {
                    // 标准输出当中执行, 不能连续出现两次 <
                    printf("- myshell: synax error near unexpected token '<'");
                    return -1;
                }
                strcpy(input_file[pro_no], p);
            }
            else if(strcmp(p, ">") == 0)
            {
                output_flag[pro_no] = 1;
                p = strsep(&buf, " ");
                if(!p || strcmp(p, "") == 0) return -1;
                if(strcmp(p, ">") == 0)
                {
                    printf("- myshell: synax error near unexpected token '>'");
                    return -1;
                }
                strcpy(output_file[pro_no], p);
            }
            else strcpy(parts[pro_no][index++], p);
            p = strsep(&buf, " ");
        }

        // 使用part形成vector
        for(i = 0; i < index; ++i)
        {
            vector[pro_no][i] = parts[pro_no][i];
        }
        vector[pro_no][i] = NULL;
        pp = strsep(&bbuf, "|");

        pro_no += 1;

        // 统计子进程的数量
        child_process_cnt += 1;
    }
    return 0;
}

/**
 * 关闭管道的文件描述符
 */
void closePipe()
{
    int i = 0;
    for(i = 0; i < child_process_cnt -1; ++i)
    {
        int ret1 = close(pipe_no[i][0]);
        int ret2 = close(pipe_no[i][1]);

        /* 此处允许error
        if(ret1 < 0 || ret2 < 0)
        {
            printf("pipe close error!\n");
            exit(1);
        }
        */
    }
}

int main()
{
    int son_flag = 0;       /* 子进程标记*/
    system("stty erase ^H");
    while(1)
    {
        printf("simulate Shell # ");
        readCommand();
        son_flag = 0;
        if(strcmp(commands, "exit") == 0) 
            break;

        parse_command();                 
#ifdef DEBUG
        printf("子进程的数量 %d\n", child_process_cnt);
#endif
        int i = 0;
        for(i = 0; i < child_process_cnt; ++i) 
        {

            // 创建新的管道
            if(child_process_cnt > 1 && i != child_process_cnt - 1)
            {
                int ret = pipe(pipe_no[i]);
                
                if(ret < 0)
                {
                    printf("create pipe error\n");
                    exit(1);
                }
            }
            
            // 释放之前的管道
            if(i >= 2)
            {
                close(pipe_no[i - 2][0]);
                close(pipe_no[i - 2][1]);
            }
            int child_pid = fork();
            if(child_pid == 0)
                break;
            
            
        }
        
        if(i < child_process_cnt)
        {
#ifdef DEBUG
        printf("我是 %d 号进程, 我执行的指令是 #%s#\n", i, parts[i][0]);
#endif
            // 先进行文件的重定向操作
            if(input_flag[i] == 1)
            {
                int fd = open(input_file[i], O_RDONLY);
                if(fd < 0)
                {
                    printf("- myshell: %s: No such file or directory\n", input_file);
                    exit(1);               
                }
                dup2(fd, STDIN_FILENO);
            }
            
            if(output_flag[i] == 1 && i == child_process_cnt - 1)
            {
                int fd = open(output_file[i], O_WRONLY | O_CREAT | O_TRUNC, 0644);
                if(fd < 0)
                {
                    printf("- myshell: %s :cant't create the file \n", output_file);
                    exit(1);
                }
                dup2(fd, STDOUT_FILENO);   
            }
            
            // 管道处理
            if(child_process_cnt > 1)
            {
                if(i == 0)
                {
                    close(pipe_no[i][0]);                       /* 关闭读端*/
                    dup2(pipe_no[i][1], STDOUT_FILENO);         /* 标准输出重定向到写端*/
                }
                else if(i == child_process_cnt - 1)
                {
                    close(pipe_no[i - 1][1]);                   /* 关闭写端*/
                    dup2(pipe_no[i - 1][0], STDIN_FILENO);      /* 标准输入重定向到读端*/
                }
                else
                {
                    close(pipe_no[i - 1][1]);                   /* 关闭上一个管道写端*/
                    dup2(pipe_no[i - 1][0], STDIN_FILENO);      /* 重定向上一个管道为输入*/
                    
                    close(pipe_no[i][0]);                       /* 关闭读端*/
                    dup2(pipe_no[i][1], STDOUT_FILENO);         /* 标准输出重定向到写端*/
                }
            }

            int ret = execvp(parts[i][0], vector[i]);
            if(ret < 0)
            {
                printf("%s : command not found or params error\n", parts[0]);
                exit(1);
            }
        }
        else
        {
            // 父进程回收子进程完毕后, 清理子进程数量标记, 子进程重定向标记, 关闭pipe
            closePipe();
            int ret = 0;
            do
            {
                ret = wait(NULL);
            }while(ret > 0);

            child_process_cnt = 0;
            memset(input_flag, 0, sizeof(input_flag));
            memset(output_flag, 0, sizeof(output_flag));
        }
    }
    return 0;
}

##### difficulty, discovery, summary:
  • fgets read end of the string will be added \ n-;

        fgetsAdd in the end \nwill lead to strsepthe end of the last of a string of isolated there \n, will lead to execvpa function parameter parsing errors.

  • Terminal input will be ^H, ^[[D, ^[[Ccontrol characters

        Command stty erase ^Hcan be solved. But every time you restart the terminal problem still exists, so each time you start the program to simulate the shell when it is executed once this directive can be.

  • Multiple redirects:

        The following command specifies multiple input and output redirection, it will only be for the last time that input and output redirection valid.

cat < a.c < b.c 
ps > a.dat > b.dat
  • strsepPit

        After the test strseptime at the beginning and end delimiters to meet extra empty string is generated, when the pipe is divided, generates &nbsp指令and 指令 >&nbsp;circumstances, so the use of &nbspthe case as the delimiter time, it is necessary to skip the empty string, and redirected file name can not be empty string judgment.

  • How to manage file descriptors pipeline

        After analysis, only in the number of child processes> 1 only to create a pipeline, and the pipeline descriptor management, we divided into three sub-processes, the first sub-process, in the middle of the child, and the last one child process.

        The first sub-process off the output terminal, and redirect its standard output to the input.

        And intermediate child closes the pipe between the input of a sub-process, and standard input and output redirection conduit. Closed conduit between the output terminal and the next sub-process, and standard output to the input end of the tube.

        For the last sub-off process input to redirect its standard input and output end of the tube.

        Difficult problem: to ensure that each pipe i I just want to keep an input and an output, because, like cat grepsuch as instruction cycle will block waiting for input. If there are multiple write the corresponding pipe end, then on a directive This directive will not end finished.

  • Pipe output redirection and redirect the output file conflict

        For example, the instruction:

ps -aux > 1.dat | cat

        psThe standard output is redirected to a file can also be redirected to the write end of the pipe, the pipe will be redirected to cover the file. But for the above instructions, we can not create out of a file, you need to pay attention.

  • Redirection and piping input file input redirection of conflict

        For example, the instruction:

cat | cat < 1.dat

        Analysis system shellafter the performance of individuals, guess input file overwrites the pipeline input, resulting in output for the second instruction process which reads data from 1.dat output end, the process will wait for the first child reads from standard input, output write to the pipe ends, but this is not the read end of the pipe, so the kernel of this process to send a SIGPIPEsignal, leading to the end of this process, command analysis is completed.

Published 17 original articles · won praise 23 · views 3403

Guess you like

Origin blog.csdn.net/DO_HACKER/article/details/104591861