实现简单的shell

实验要求:

  • 实现 管道 (也就是 |)
  • 实现 输入输出重定向(也就是 < > >>)
  • 实现 后台运行(也就是 & )
  • 实现 cd,要求支持能切换到绝对路径,相对路径和支持 cd -
  • 屏蔽一些信号(如 ctrl + c 不能终止)
  • 界面美观
  • 开发过程记录、总结、发布在个人博客中

这次实验于13日结束,最近太混了,今天才发博客

思路:

  • 主体框架

  • void type_prompt(void); //终端提示符

  • read_command()//读取参数

  • split_command()//解析参数

  • 执行command

  • 内建命令

shell实现的基本逻辑简单分为三部:读取,分析,执行

我们可以写一个loop函数,也可以放在main函数里

​
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <cstring>
#include <pwd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>

#define LSH_TOK_DELIM " \t\r\n\a"
#define MAXPIDTABLE 1024
#define NONE 0
#define BACKGROUND 1
#define IN_REDIRECT 2
#define OUT_REDIRECT 4
#define OUT_REDIRECT_CL 16
#define IN_REDIRECT_CL 8
#define PIPE 32

void type_prompt(void);        //终端提示符,如果当前路径在用户路径下,那么用户路径就用~代替,否则会显示完整路径。
char *read_line(void);         //从标准输入读取一行
char **split_line(char *line); //将该行解析为参数列表
char execute(char **args);     //
int shell_cd(char **args);     //实现cd

int num;
pid_t BPTable[MAXPIDTABLE];

int main(int argc, char *argv[])
{
    char *line;
    char **args;
    int status;
    do
    {
        type_prompt();
        line = read_line();
        args = split_line(line);
        status = execute(args);
    } while (status);
    return 0;
}

​

 实现一个简易的type_prompt

顾名思义,这个要提供一个终端上的提示符8359072babce4945bbbff6ed3404481c.png

void type_prompt(void)
{
    struct passwd *name;
    name = getpwuid(getuid());
    char path[255] = {0};
    getcwd(path, 255); // getcwd() 会将当前的工作目录绝对路径复制到参数buf 所指的内存空间
    printf("%s@PC:", name->pw_name);
    if (strlen(path) < strlen(name->pw_dir) || strncmp(path, name->pw_dir, strlen(name->pw_dir)) != 0)
        printf("%s ", path);
    else
        printf("~%s ", path + strlen(name->pw_dir)); //缩进
    if (geteuid() == 0)
        printf("#");
    else
        printf("$");
    return;
}

事先不提前知道用户将在其 shell 中输入多少文本。不能简单地分配一个块并希望它们不会超过它。相反,需要从一个块开始,如果它们超过它,重新分配更多空间。

我么可以使用getline函数,完成我们刚刚实现的大部分工作。

getline()函数:

 https://blog.csdn.net/qq_26093511/article/details/73350912

char *read_line(void)
{
    /*   char *line = NULL;
        size_t bufsize = 0;
        if (getline(&line, &bufsize, stdin) == -1) //失败:返回-1。
        {
            if (feof(stdin)) // C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。
                exit(0);     // EOF
            else
            {
                perror("readline");
                exit(-1);
            }
        }
        return line;
    */

    size_t bufsize = 1024;
    int position = 0;
    char *buffer = (char *)malloc(sizeof(char) * bufsize);
    int c;

    if (!buffer)
    {
        fprintf(stderr, "allocation error\n");
        exit(EXIT_FAILURE);
    }

    while (1)
    {
        // Read a character
        c = getchar();

        if (c == EOF)
        {
            exit(EXIT_SUCCESS);
        }
        else if (c == '\n')
        {
            buffer[position] = '\0';
            return buffer;
        }
        else
        {
            buffer[position] = c;
        }
        position++;

        if (position >= bufsize)
        {
            bufsize += 1024;
            buffer = (char *)realloc(buffer, bufsize);
            if (!buffer)
            {
                perror("allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
}

现在,我们需要将该行解析为参数列表。

此时,我们使用strtok函数

C 库函数char *strtok(c​​har *str, const char *delim)使用分隔符delim将字符串str分解为一系列标记。

宣言

以下是 strtok() 函数的声明。

<span style="color:rgba(0, 0, 0, 0.87)"><span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">strtok</span><span style="color:#666600">(</span><span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">str</span><span style="color:#666600">,</span> <span style="color:#000088">const</span> <span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">delim</span><span style="color:#666600">)</span></span>

参数

  • str - 此字符串的内容被修改并分解为更小的字符串(令牌)。

  • delim - 这是包含分隔符的 C 字符串。这些可能因一个呼叫而异。

 

 在函数开始时,我们通过调用来开始标记化strtok。它返回一个指向第一个标记的指针。实际上所做的是返回指向您strtok()给它的字符串中的指针,并将\0字节放在每个标记的末尾。我们将每个指针存储在字符指针数组(缓冲区)中。

char **split_line(char *line)
{
    size_t bufsize = 1024;
    int position = 0;
    char **tokens = (char **)malloc(bufsize * sizeof(char *));
    memset(tokens, 0, 1024);
    char *token;
    if (!tokens)
    {
        puts("allocation error\n");
        exit(EXIT_FAILURE);
    }

    token = strtok(line, LSH_TOK_DELIM);
    while (token != NULL)
    {
        tokens[position] = token;
        position++;
        if (position >= bufsize)
        {
            bufsize += 1024;
            tokens = (char **)realloc(tokens, bufsize * sizeof(char *));
            if (!tokens)
            {

                puts("allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
        token = strtok(NULL, LSH_TOK_DELIM);
    }
    num = position;
    tokens[position] = NULL;
    return tokens;
}

 在execute中,对">""|"等进行判断。

char execute(char **args)
{

    if (args[0] == NULL)
        return 1;

    if (strcmp(args[0], "cd") == 0)
        return shell_cd(args);

    int flag = 0, i, fd;
    char *command = NULL;

    pid_t childpid, childpid2;
    int status;
    for (i = 0; i < num; i++)
    {
        if (strcmp(args[i], "|") == 0)
        {
            flag += PIPE;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }

        else if (strcmp(args[i], ">") == 0)
        {
            flag += OUT_REDIRECT_CL;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }
        else if (strcmp(args[i], ">>") == 0)
        {
            flag += OUT_REDIRECT;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }
        else if (strcmp(args[i], "<") == 0)
        {
            flag += IN_REDIRECT_CL;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }
        else if (strcmp(args[i], "<<") == 0)
        {
            flag += IN_REDIRECT;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }
        else if (strcmp(args[i], "&") == 0)
        {
            flag += BACKGROUND;
            args[i] = NULL;
            if (args[i + 1] != NULL)

                command = args[i + 1];

            else
                perror("输入错误/n");
        }
    }
    childpid = fork();

    switch (flag)
    {

    case NONE:
    {

        if (execvp(args[0], args) == -1)
        {
            perror("execute");
            exit(EXIT_FAILURE);
        }

        break;
    }

    case PIPE:
    {
        int pipe_fd[2], in_fd, out_fd;
        if (pipe(pipe_fd) < 0)
        {
            printf("shell error:pipe failed.\n");
            exit(0);
        }
        if ((childpid2 = fork()) == 0)
        {
            close(pipe_fd[1]);
            close(fileno(stdin));
            dup2(pipe_fd[0], fileno(stdin));
            close(pipe_fd[0]);
            execvp(args[0], args);
            exit(EXIT_SUCCESS);
        }

        else
        {
            close(pipe_fd[0]);
            close(pipe_fd[1]);
            waitpid(childpid2, &status, 0);
        }
        break;
    }

    case OUT_REDIRECT_CL:
    {
        fd = open(command, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);
        dup2(fd, 1);
        execvp(args[0], args);
        exit(0);
        break;
    }

    case OUT_REDIRECT:
    {
        fd = open(command, O_RDWR | O_CREAT | O_APPEND | S_IRWXU);
        dup2(fd, 1);
        execvp(args[0], args);
        exit(0);
        break;
    }

    case IN_REDIRECT:
    {
        fd = open(command, O_CREAT | O_RDONLY);
        dup2(fd, 0);
        execvp(args[0], args);
        exit(0);
        break;
    }

    case IN_REDIRECT_CL:
    {
        fd = open(command, O_CREAT | O_RDONLY);
        dup2(fd, 0);
        execvp(args[0], args);
        exit(0);
        break;
    }

    case BACKGROUND:
    {
        printf("Child pid:%u\n", childpid);
        for (i = 0; i < MAXPIDTABLE; i++)
            if (BPTable[i] == 0)
                BPTable[i] = childpid;
        if (i == MAXPIDTABLE)
            perror("Too much background processes/n");
        break;
    }
    default:
        perror("输入错误!\n");
        break;
    }

    if (childpid < 0)
        perror("execute2");

    else
    {
        do
        {
            waitpid(childpid, &status, WUNTRACED);

        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }

    free(args);
    return 1;
}

实现cd

​
int shell_cd(char **args)
{

    if (args[1] == NULL || args[1] == "~")
    {
        struct passwd *name;
        name = getpwuid(getuid());
        char *path = (char *)calloc(255, sizeof(char));
        char *ar = (char *)calloc(255, sizeof(char));
        strcpy(ar, name->pw_name);
        strcpy(path, "/home/");
        strcat(path, ar);
        strcat(path, "/");
        chdir(path);
    }

    else
    {
        if (chdir(args[1]) != 0)
        {
            perror("cd error");
        }
    }

    return 1;
}

​

 使用valgrind检测:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAV2VpbmllbnhpYWZqaWFu,size_20,color_FFFFFF,t_70,g_se,x_16

 实验不足:

1.代码仍有Bug未解决。

2.个别要求未满足。

3.第一次写完代码后使用valgrind出现大量段错误未解决,于是重构代码。

参考资料: 

shell 的实现:https://www.cnblogs.com/wuyuegb2312/p/3399566.html

                      https://brennan.io/2015/01/16/write-a-shell-in-c/

valgrind:Valgrind详细教程(1) Memcheck_tissar的博客-CSDN博客_valgrind教程 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值