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\n", cmds[i]);
            i++; */
            pid = fork();
            if (pid < 0) {
                perror("fork");
                exit(1);
            } else if (pid == 0) {   // 当前指令子进程
                /* printf("1\n"); */
                if (i > 0) {         // 若有上一条,读上一pipe并关闭写端
                    /* printf("2\n"); */
                    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], '\n');
            *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 = '\0';
        *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$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值