Linux下实现简单的shell解释器

时间:2022-05-28 14:24:50

什么是shell

shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接受用户输入的命令并把它送入内核去执行。
实际上shell就是一个命令解释器,它解释有用户输入的命令并且把它们送到内核。不仅如此,shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序。shell变成语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等,用这种编程语言编写的shell程序与其他应用程序具有同样的效果。

shell运行原理

shell就是运用进程的程序替换的原理进行实现的。
何为程序替换?
假如操作系统正在执行某一个程序,然后我们利用程序替换函 数指定一个新的程序,让操作系统去执行我们新指定的程序。也就是这样一种情形下,我们fork一个进程,如果fork成功,子进程会和父进程执行相同的代码,而我们创建子进程是希望子进程执行指定的操作,所以需要执行exec族函数。
何为程序替换函数?
程序替换函数是一族函数,可以通过man命令进行查看。
其实有六种以exec开头的函数,统称exec函数:

    #include <unistd.h>
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg, ..., char *const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execve(const char *path, char *const argv[], char *const envp[]);

这些函数如果调⽤用成功则加载新的程序从启动代码开始执⾏行,不再返回,如果调⽤用出错则返回-1, 所以exec函数只有出错的返回值⽽而没有成功的返回值。

不带字母p (表⽰示path)的exec函数 第⼀一个参数必须是程序的相对路径或绝对路径,例如”/bin/ls”或”./a.out”,而不能 是”ls”或”a.out”。对于带字母p的函数: 如果参数中包含/,则将其视为路径名。 否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。

带有字母l( 表⽰示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有…,…中的最后一个可变参数应该是NULL, 起sentinel的作用。

带有字母v( 表⽰示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后⼀个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。

对于以e (表⽰示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。

模拟实现一个简单shell

实现步骤:
1)采用read函数读取标准输入写到显示器上的信息,并且返回读取到的字符个数,如果返回值大于0,说明有读取到字符,就将读到的字符串的最后加个字符串的结束符\0;否则,结束此次循环。
2)将读取到的字符串按照空格分成多个字符串,放进指针数组argv中,并在指针数组的最后加一个NULL。
3)创建一个子进程,父进程等待子进程执行完程序,子进程执行程序替换函数,关于程序替换函数的选择:我们选择的是execvp函数(因为我们已经知道要执行的程序的文件名,参数也已经全部存储在argv指针数组中)。

代码实现

//myshell.c

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

//获取登陆用户名
void GetLoginName(){
    struct passwd* pass;
    pass = getpwuid(getuid());
    printf("[%s@ ",pass->pw_name);
}

//获取主机名
void GetHostName(){
    char name[128];
    gethostname(name,sizeof(name)-1);
    printf("%s",name);;
}

//获取当前文件路径
void GetDir(){
    char pwd[128];
    getcwd(pwd,sizeof(pwd)-1);
    int len = strlen(pwd);
    char *p = pwd+len-1;
    while(*p != '/' && len--){
        p--;
    }
    p++;
    peintf("%s] #",p);
}

int main()
{
    while(1){
        GetLoginName();
        GetHostName();
        GetDir();
        char cmd[128];
        fflush(stdout); //清空输出缓冲区
        //读取字符
        int s = read(0,cmd,sizeof(cmd)-1);
        if(s > 0){
            cmd[s-1] = '\0';
            int i = 0;
            for(; i < s; ++i){
                int j = 0;
                for(j = i+1; j < s; ++j){
                    cmd[j-2] = cmd[j];
                }
                cmd[s-2] = 0;
            }
            else if(cmd[i] == '\b' && i == 0){
                int j = 0;
                for(j = 1; j < s; ++j){
                    cmd[j-1] = cmd[j];
                }
                cmd[s-1] = 0;
            }
            else{
                continue;
            }
        }
        cmd[s] = 0;
    }
    else{
        continue;
    }

    char *_argv[34];
    _argv[0] = cmd;
    int i = 1;

    char *start = cmd;
    while(*start){
        if(isspace(*start)){
            *start = 0;
            start++;
            _argv[i] = start;
            i++;
            continue;
        }
        start++;
    }
    _argv[i] = NULL;
    pid_t id = fork();
    if(id < 0){
        perror("fork");
    }else if(id == 0){//child run cmd
        exexvp(_argv[0], _argv);
        exit(1);
    }else{
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        if(ret > 0 && WIFEXITED(status)){
        }else{
            perror("waitpid");
        }
    }
    }
}

//Makefile

myshell:myshell.c
    gcc -o myshell myshell.c
.PHONY:clean
clean:
    rm -f myshell

结果展示

Linux下实现简单的shell解释器