Linux 模拟实现shell【简单实现】

shell的模拟实现

我们知道shell是一个永不退出的程序,所以他应该是一个死循环,并且shell为了防止影响到自己,我们在命令行上输入的所有命令都是由shell的子进程来执行的,所以它应该要有创建子进程的相关函数,当然也会有进程替换的相关函数,因为我们直接创建子进程,父子进程是共享代码的,如果没有进程替换,shell根本无法让子进程执行特定的命令。

总的思路:
1.打印提示符 && 获取用户命令字符串
2.分割字符串
//“ls -a -l” —> “ls” “-a” “-l”
3.check buildin command 检查内键命令
4.执行命令

1.打印提示符 && 获取用户命令字符串

在这里插入图片描述

对于getUserCommand函数:

先介绍fgets函数:在这里插入图片描述

#define NUM 1024
#define SIZE 64

int getUserCommand(char *command,int num)
{
    printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());

    //不用scanf,遇到空格会停,不好用
    char *r = fgets(command,num,stdin);//最终还是会输入\n
    if(r == NULL) return -1;
    //abcd\n
    command[strlen(command) - 1] = '\0';//给最后加上\0
    return strlen(command);
}

getUsername(),getHostname(),getCwd() 是为了读取用户名@主机名 当前所在文件夹

在这里插入图片描述

2.分割字符串

比如我要执行ls命令,ls命令后还可以携带选项,ls -a -l,shell需要获取指令与选项,并将它们分割(shell接收到的是一串字符串"ls -a -l",shell需要将字符串分割为更小的字符串"ls" “-a” “-l”)。

定义char usercommand[NUM];接收一整行的指令,该数组中的元素保存的是字符,char *argv[SIZE];//存储命令拆分后结果,该数组中的元素要保存的是分割后的字符串。
在这里插入图片描述

介绍strtok:
在这里插入图片描述

void commandSplit(char *in,char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in," ");//分隔并放入out指针数组
    while(out[argc++] = strtok(NULL," "));
}

3.check buildin command 检查内键命令

在这里插入图片描述

内建命令:

开头说到,shell的命令分为内部命令和其他命令,何为内建命令? 内建命令是一个需要shell自己执行的命令,即shell不创建子进程,自己亲自执行的命令。

shell对于一些命令是必须要亲自执行的:比如cd更改工作路径,将shell的工作路径修改后,由于子进程的工作路径与父进程相同,更改分进程的工作路径后,父进程创建出的子进程的工作路径也是被修改过的,体现给用户的感觉就是当前的工作路径改变了。

对于内建命令,shell是怎么实现的? shell中有许多内建命令,当shell接收到指令并解析后,需要判断用户输入的命令是否为内建命令,如果是就执行拦截操作,使子进程不再被创建,自己执行该指令。如果不是内建命令,则创建子进程执行该命令。

内建命令太多,代码里面只写了cd和export内建命令。

char cwd[1024];//全局变量
char enval[1024];//for test 

char *homepath()
{
    char *home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

void cd(const char *path)
{
    chdir(path);//chdir(),用户将当前的工作目录改变成以参数路径所指的目录
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));//getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。
    sprintf(cwd,"PWD=%s",tmp); //打印到字符串中
    putenv(cwd);//putenv 函数会将cwd 直接填写到环境表中
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0) //cd ...
    {
        char *path =  NULL;
        if(argv[1] == NULL) path = homepath();//纯cd,回到HOME
        else path = argv[1];
        cd(path);//进入要进入的路径
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval,argv[1]);//拷贝
        putenv(enval);//直接填写到环境表中
        return 1;
    }
    return 0;
}

对于void cd(const char *path),用 chdir() 函数来将 用户当前的工作目录改变成以参数路径所指的目录,
用 getcwd() 函数将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,
用 sprintf() 函数将 “PWD=工作目录” 字符串打印给cwd,
用putenv 将cwd 直接填写到环境表中。

4.执行命令

在这里插入图片描述

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
        //exec command
        execvp(argv[0],argv);//execvp()会从环境变量所指的目录中查找符合参数 file 的文件名, 找到后执行该文件, 然后将第二个参数argv 传给该执行的文件。
        exit(1);
    }
    else //father
    {
        int status = 0;
        pid_t res = waitpid(-1, &status, 0);//阻塞式等待
        if(res > 0)//等待成功
        {
            printf("exit code: %d \n", WEXITSTATUS(status));
        }
    }

    return 0;
}

用子进程进行进程替换,执行命令:

在进行进程替换时,使用ececvp()函数:
在这里插入图片描述

效果动态图

在这里插入图片描述

代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define NUM 1024
#define SIZE 64


char cwd[1024];
char enval[1024];//for test 

char *homepath()
{
    char *home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

const char *getUsername()
{
    const char *name = getenv("USER");
    if(name) return name;
    else return "none";
}

const char *getHostname()
{
    const char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}

const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}

int getUserCommand(char *command,int num)
{
    printf("[%s@%s %s]",getUsername(),getHostname(),getCwd());

    //不用scanf,遇到空格会停,不好用
    char *r = fgets(command,num,stdin);//最终还是会输入\n
    if(r == NULL) return -1;
    //abcd\n
    command[strlen(command) - 1] = '\0';
    return strlen(command);
}

void commandSplit(char *in,char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in," ");//分隔并放入out指针数组
    while(out[argc++] = strtok(NULL," "));
}

void cd(const char *path)
{
    chdir(path);//chdir(),用户将当前的工作目录改变成以参数路径所指的目录
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));//getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。
    sprintf(cwd,"PWD=%s",tmp); //打印到字符串中
    putenv(cwd);//putenv 函数会将c wd 直接填写到环境表中
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0) //cd ...
    {
        char *path =  NULL;
        if(argv[1] == NULL) path = homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval,argv[1]);
        putenv(enval);
        return 1;
    }

    return 0;
}

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
        //exec command
        execvp(argv[0],argv);//execvp()会从环境变量所指的目录中查找符合参数 file 的文件名, 找到后执行该文件, 然后将第二个参数argv 传给该执行的文件。
        exit(1);
    }
    else //father
    {
        int status = 0;
        pid_t res = waitpid(-1, &status, 0);//阻塞式等待
        if(res > 0)//等待成功
        {
            printf("exit code: %d \n", WEXITSTATUS(status));
        }
    }

    return 0;
}

int main()
{
    while(1)
    {
        char usercommand[NUM];//存储命令
        char *argv[SIZE];//存储命令拆分后结果
        //1.打印提示符 && 获取用户命令字符串
        int n = getUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0) continue;//输入空也可以重新输入
        //2.分割字符串
        //"ls -a -l" ---> "ls" "-a" "-l"
        commandSplit(usercommand,argv);
        //3.check buildin command 检查内键命令
        n = doBuildin(argv);
        if(n) continue;
        //4.执行命令
        execute(argv);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值