• Linux C编程一站式学习编程练习:实现简单的Shell


    Linux C编程一站式学习P585编程练习:

    实现简单的Shell

    用讲过的各种C函数实现一个简单的交互式Shell,要求:

    1. 给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的exec函数执行程序,待执行完成后再次给出提示符。
    2. 识别和处理以下符号:
      · 简单的标准输入输出重定向(<和>):仿照例30.5 “wrapper”,先dup2然后exec。
      · 管道(|):Shell进程先调用pipe创建一对管道描述符,然后fork出两个子进程,一个子进程关闭读端,调用dup2把写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait等待两个子进程终止。

    你的程序应该可以处理以下命令:
    ○ls△-l△-R○>○file1○
    ○cat○<○file1○|○wc△-c○>○file1○
    ○表示零个或多个空格,△表示一个或多个空格

    思路:

    main函数(主进程)以一个循环从标准输入stdin不断读取指令,直到ctrl+c退出。读取的指令首先作为字符串存放在input_str中,然后调用split函数对其进行拆分,根据管道(|)切割成多条指令,存放在cmds中。程序假设拆分后得到的指令最多10条。

    按顺序处理cmds中的每一条指令:对每条指令,fork一个子进程并调用exec函数处理它。exec函数中使用take_token函数对指令根据空格和重定向符进行拆分,拆分后得到记录执行文件和参数的数组token(token[0]为指令的可执行文件名,作为execvp函数的第一个参数;整个token数组作为execvp函数的第二个参数)。若有重定向符">“或”<"存在,则读取紧跟重定向符的字符串作为文件名,根据符号重定向输入或输出到指定文件。
    程序中使用管道fd[10][2]作为多条指令输入输出之间的通信手段:假设当前执行的为第n条指令

    • 若当前指令有上一条指令,从管道fd[n-1][0]读取上一条指令的输出作为输入,并关闭管道写端fd[n-1][1](57~61行);
    • 若当前指令有下一条指令,重定向其输出到管道fd[n][1]被下一条指令读取,并关闭管道读端fd[n][0](63~65行);
    • 若当前指令是最后一条指令,恢复输出到标准输出stdout;
    • 以上步骤中子进程都由父进程fork而来,因此属于子进程之间通过管道相互通讯,父进程需要关闭管道的读写两段。

    在程序中我的逻辑是,管道fd[n]沟通第n和第n+1个指令,因此fork出第n+1个子进程时,父进程可以关闭第n个(即前一个)管道的读写端。
    (这里每次没关干净管道,下此循环开始又调用pipe,不知道会不会有泄露之类的。。实在不太懂,如果有人能帮我指出就非常感谢_(:3」∠)_)

    我的程序源代码如下:

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #define BUFF 512
    
    int split(char input_str[], char *cmds[]);
    void take_token(char cmd[], char *token[], char *file[]);
    void exec(char cmd[]);
    
    /* test:
    cat < file1|wc -c > file1
    cat<file1 | wc -c > file1
    cat     < file1| wc -c>file1
    cat < file1 | wc -c > file1     
    ls -l -R > file1
    ls -l -R>   file1
    ls -l -R >file1   
    */
    
    int main()
    {
        char input_str[BUFF];  // 输入指令字符串
        char *cmds[10];        // 分割完的指令字符串
        int fd[10][2];         // 管道
        int i, pid, cmd_num;
        int save_stdin, save_stdout;
        
        save_stdin = dup(STDIN_FILENO);
        save_stdout = dup(STDOUT_FILENO);
        
        while (1) {
            printf("username:path$ ");
            fgets(input_str, BUFF, stdin);
            cmd_num = split(input_str, cmds);
            
            if (cmd_num > 1) {
                if (pipe(fd[0]) < 0) {   // 当前pipe
                    perror("pipe");
                    exit(1);
                }
            }
            
            i = 0;
            while (cmds[i] != NULL) {
    /*             printf("%s
    ", cmds[i]);
                i++; */
                pid = fork();
                if (pid < 0) {
                    perror("fork");
                    exit(1);
                } else if (pid == 0) {   // 当前指令子进程
                    /* printf("1
    "); */
                    if (i > 0) {         // 若有上一条,读上一pipe并关闭写端
                        /* printf("2
    "); */
                        close(fd[i-1][1]);
                        dup2(fd[i-1][0], STDIN_FILENO);
                    }
                    if (cmds[i+1] != NULL) {     // 若有下一条,写当前pipe并关闭读端
                        close(fd[i][0]);
                        dup2(fd[i][1], STDOUT_FILENO);
                    } else {   // 当前指令是最后一条,恢复标准输出
                        dup2(save_stdout, STDOUT_FILENO);
                    }
                    exec(cmds[i]);
                } else {                 // 当前指令父进程
                    if (cmds[i+1] != NULL && cmds[i+2] != NULL) {
                        if (pipe(fd[i+1]) < 0) {   // 当前pipe
                            perror("pipe");
                            exit(1);
                        }
                    }
                    
                    if (i > 0) {         // 两次fork后关闭上一条指令的父进程pipe读写
                        close(fd[i-1][0]); // 顺序:fork子1,fork子2,关闭父读写
                        close(fd[i-1][1]); // 这个if块写在i++后面会阻塞子进程。。
                    }
                    waitpid(pid, NULL, 0);           // 等待指令执行结束
                    i++;
    
                }
            }
        }
        return 0;
    }
    
    int split(char input_str[], char *cmds[])
    {
        int i = 0; // 指令个数
        char *str = NULL, *saveptr = NULL;
        char *enter = NULL;
        
        for (i=0, str=input_str; ; i++, str=NULL){
            cmds[i] = strtok_r(str, "|", &saveptr);
            if (cmds[i] == NULL) {
                enter = strrchr(cmds[i-1], '
    ');
                *enter = ' ';   // 替换最末尾换行符
                break;
            }
        }
        return i;
    }
    
    void take_token(char cmd[], char *token[], char *file[]) {
        int i;
        char *op;
        char *str = NULL, *saveptr = NULL;
        int fd, std_fileno, file_mode;
        
        if ((op = strrchr(cmd, '<')) != NULL) {
            std_fileno = STDIN_FILENO;
            file_mode = O_RDONLY;
        }
        else if ((op = strrchr(cmd, '>')) != NULL) {
            std_fileno = STDOUT_FILENO;
            file_mode = O_WRONLY | O_CREAT | O_TRUNC;
        }
        
    
        if (op) {
            *op = '';
            *file = strtok_r((op+1), " ", &saveptr);
            fd = open(*file, file_mode, 0666);
            if (fd < 0) {
                perror("open");
                exit(1);
            }
    
            dup2(fd, std_fileno);
    
            //printf("[[%s]]", *file);
        }
    
        for (i=0, str=cmd, saveptr = NULL; ; i++, str=NULL) {
            token[i] = strtok_r(str, " ", &saveptr);
            if (token[i] == NULL)
                break;
        }
        return ;
    }
    
    void exec(char cmd[])
    {
        char *tokens[100];
        char *str, *saveptr, *file;
        int i, mode;
        
        take_token(cmd, tokens, &file);
    
        execvp(tokens[0], tokens);
        perror(tokens[0]);
        return ;
    }
    

    执行结果示例:

    qxy@qxy:/mnt/c/Users/saltyfish/Desktop/test$ ./test1
    username:path$ ls
    1.jpg  clean  file1  file2  img  Makefile  pytest  test  test1  test1.c  test1.o  test.cpp  tmp  tmp.c
    username:path$ ls -l
    total 72
    -rwxrwxrwx 1 qxy qxy  7448 Dec 11  2017 1.jpg
    drwxrwxrwx 1 qxy qxy  4096 Oct  3 14:59 clean
    -rwxrwxrwx 1 qxy qxy 10216 Oct 17 12:45 file1
    -rwxrwxrwx 1 qxy qxy     4 Oct  3 14:26 file2
    drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 img
    -rwxrwxrwx 1 qxy qxy    93 Jun 23  2017 Makefile
    drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 pytest
    drwxrwxrwx 1 qxy qxy  4096 Oct  3 15:21 test
    -rwxrwxrwx 1 qxy qxy 13560 Oct 17 12:26 test1
    -rwxrwxrwx 1 qxy qxy  4420 Oct 17 12:26 test1.c
    -rwxrwxrwx 1 qxy qxy  4784 Oct 17 12:26 test1.o
    -rwxrwxrwx 1 qxy qxy   974 Mar 25  2018 test.cpp
    -rwxrwxrwx 1 qxy qxy  9936 Sep 23 10:06 tmp
    -rwxrwxrwx 1 qxy qxy   779 Oct 16 22:35 tmp.c
    username:path$ ls -l -R > file1
    username:path$ cat <file1 | wc -c >file2
    username:path$ cat file2
    10216
    username:path$
    
  • 相关阅读:
    安卓手机无法使用adb导出文件
    dex2jar 报错 com.googlecode.d2j.DexException: not support version
    夜神模拟器adb连接
    无法安装 /lib/x86_64-linux-gnu/libpng12.so.0 的新版本: 没有那个文件或目录
    mysql基础 -创建
    VScode
    阿强的TypeScript基础
    Vue由浅入深之Array变化侦测
    深入浅出Vue.js一之Object的变化侦测
    阿强工作中常用的js的数组方法汇总
  • 原文地址:https://www.cnblogs.com/sssaltyfish/p/10656767.html
Copyright © 2020-2023  润新知