简易 shell

目录

 1.打印提示符并且获取用户字符串

2.字符串分割

3.执行命令 

<1>: 内建命令 

<2>:export 的实现 

<3>: echo 的实现 

4.可执行文件加颜色 

5.完整代码


makefile 文件内容:

myshell:myshell.c
		gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
		rm -f myshell

 1.打印提示符并且获取用户字符串

当我们登录 xshell 时,它的命令行提示符为:

[zrsg@hecs-393616 test_12_10]$ 

当我们自行实现 shell 时,需要我们打印出这一行的内容,而这一行的内容和当前所处的环境变量息息相关,获取环境变量的命令是:env 

查看当前环境变量:

 我们主要关注以上标黄色框框的部分。

获取环境变量的函数为 getenv():

在 myshell.c 文件中,代码为:

#include<stdio.h>
#include<stdlib.h>
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 main()
{
    printf("[%s@%s %s]# \n",getUsername(),getHostname(),getCwd());
    return 0;
}

运行效果为:

有了命令提示符之后,我们发现程序直接结束,而显示使用过程中,光标应该卡住,需要用户进行输入,接下来我们对代码进行修改,加一个宏定义,和对 main 函数进行修改,修改内容如下:

#define NUM 1024//新增

int main()//添加内容
{
    char usercommand[NUM];
    printf("[%s@%s %s]# \n",getUsername(),getHostname(),getCwd());
    //输入
    scanf("%s",usercommand);
    return 0;
}

运行效果为:

达到了我们想要的结果,但是在我们使用 shell 的时候我们不难发现,我们的光标是在行末的,此时我们对 main 函数中的代码进行修改:

int main()
{
    char usercommand[NUM];
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    //输入
    scanf("%s",usercommand);
    return 0;
}

运行效果为:

此时的光标在等候我们进行输入,我们可以先对输入的内容进行一个回显,看我们输入的内容 main 函数代码为:

int main()
{
    char usercommand[NUM];
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    //输入
    scanf("%s",usercommand);
    printf("echo:\n%s\n",usercommand);
    return 0;
}

运行效果为: 

此时,我们会发现在回显时,仅回显了 ls ,为什莫???

scanf 在读取时,遇到空格会自动停下来!!! 


fatges --- 获取命令行


使用 fgets 对 main 函数再次进行修改,修改后 main 函数代码为:

int main()
{
    char usercommand[NUM];
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    //输入
    char* tmp = fgets(usercommand,sizeof(usercommand),stdin);
    if(tmp == NULL)
    {
        return 1;
    }
    printf("%s\n",usercommand);
    return 0;
}

运行效果为:

我们会发现,在回显之后多了一个空行???

在此处,我们可以尝试去掉 printf("%s\n",usercommand); 中的 \n ,去掉后的运行效果为:

我们会发现,此处,仍然另起了新的一行,并没有跟在回显内容之后 !!!

原因,在我们输入 ls -a -l 之后,我们还按下了回车键,也就是说,回显内容中包含 \n 。

如何解决上述问题呢? 

由于,我们都是输入内容之后,再按下回车键,所以说 \n 一定位于最后一个位置,我们可以对字符串长度进行减一操作,从而达到去掉多余 \n 的目的。

修改后 main 函数代码为:

#include<string.h>

int main()
{
    char usercommand[NUM];
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    //输入
    char* tmp = fgets(usercommand,sizeof(usercommand),stdin);
    if(tmp == NULL)
    {
        return 1;
    }
    usercommand[strlen(usercommand) - 1] = '\0';
    printf("%s",usercommand);
    return 0;
}

usercommand[strlen(usercommand) - 1] = '\0';
在此处不会存在越界问题,在字符串中,至少有一个 \n ,因为即使为空,我们也需要按下回车键,对长度进行减一的操作可以正常进行。

为了后续更加方便,我们现在对 main 函数中的内容进行封装,即增加一个 ---

 int getUserCommand(char* command,int num) 函数,修改后代码为:

int getUserCommand(char* command,int num)
{
    char usercommand[NUM];                                                                                                        
     printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());                                                                    
    //输入                                                                                                                          
    char* tmp = fgets(command,num,stdin);                                                                       
    if(tmp == NULL)                                                                                                                 
    {                                                                                                                               
        return 1;                                                                                                                   
    }                                                                                                                               
    command[strlen(command) - 1] = '\0';                                                                                    
    return 0;
}

int main()
{
    char usercommand[NUM];
    getUserCommand(usercommand,sizeof(usercommand));
    printf("%s",usercommand);
    return 0;
}

运行效果为: 

至此,我们完成了,打印提示符并且获取用户字符串获取成功。


2.字符串分割

此时,我们需要进行字符串分割,例如:将 ls -a -l 进行分割为 "ls" "-a" "-l"

分割字符串 --- strtok:

成功返回起始地址,失败返回空。

添加和修改的代码为:

#define SIZE 64
#define SEP " "

int main()
{
    char usercommand[NUM];
    char* argv[SIZE];
    int argc = 0;
    getUserCommand(usercommand,sizeof(usercommand));
    //分割字符串
    argv[argc++] = strtok(usercommand,SEP);
    while(argv[argc++] = strtok(NULL,SEP));
    //下述for循环用于检测是否按顺序提取到各个内容
    for(int i = 0; argv[i];i++)
    {
        printf("%d:%s\n",i,argv[i]);
    }
    printf("%s",usercommand);
    return 0;
}

运行效果为:

接下来,我们对分割字符串部分进行封装,即增加一个 ---

void commandSplit(char* in,char* out[]) 函数,修改后代码为:

#define Debug 1

void commandSplit(char* in,char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in,SEP);
    while(out[argc++] = strtok(NULL,SEP));
#ifdef Debug  
    for(int i = 0; out[i];i++)
    {
        printf("%d:%s\n",i,out[i]);
    }
#endif 
}
int main()
{
    char usercommand[NUM];
    char* argv[SIZE];
    getUserCommand(usercommand,sizeof(usercommand));
    //分割字符串
    commandSplit(usercommand,argv);
    return 0;
}

运行效果为:

至此,分割字符串成功。


3.执行命令 

接下来,需要我们去执行对应的命令。

此时,需要使用程序替换,程序替换函数 --- execvp

程序替换时,不能让本程序去执行对应的命令,若为本程序执行,剩余的代码不会执行完就退出,此时,需要我们去创建子进程去执行对应的命令 --- 创建子进程 fork 

子进程被创建之后,去执行对应的命令,此时,父进程需要等待子进程,等待子进程 waitpid

代码为:

#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    char usercommand[NUM];
    char* argv[SIZE];
    getUserCommand(usercommand,sizeof(usercommand));
    //分割字符串
    commandSplit(usercommand,argv);
    //执行对应的命令
    pid_t id = fork();
    if(id < 0)
    {
        return 0;
    }
    else if(id == 0)
    {
        //子进程 --- 程序替换
        execvp(argv[0],argv);
        exit(1);
    }
    else 
    {
        //父进程 --- 等待子进程
        pid_t rid = waitpid(id,NULL,0);
        if(rid > 0)
        {
            //等待成功
        }
    }
    return 0;
}

运行效果为:

此时,命令行只跑了一次就结束了!!!

我们只需要将 main 函数中的内容放入死循环,如下图所示: 

运行效果为:

接下来,对上述执行命令部分进行修改并且进行封装:

int getUserCommand(char* command,int num)
{
    char usercommand[NUM];                                                                                                        
     printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());                                                                    
    //输入                                                                                                                          
    char* tmp = fgets(command,num,stdin);                                                                       
    if(tmp == NULL)                                                                                                                 
    {                                                                                                                               
        return 1;                                                                                                                   
    }                                                                                                                               
    command[strlen(command) - 1] = '\0';                                                                                    
    return strlen(command);
}

int execute(char* argv[])
{
     pid_t id = fork();
     if(id < 0)
     {
        return -1;
     }
     else if(id == 0)
     {
         //子进程 --- 程序替换
         execvp(argv[0],argv);
         exit(1);
     }
     else 
     {
         //父进程 --- 等待子进程
         pid_t rid = waitpid(id,NULL,0);
         if(rid > 0)
         {
             //等待成功
         }
    }
     return 0;
}
int main()
{
    while(1)
    {
        char usercommand[NUM];
        char* argv[SIZE];
        int n = getUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0)
        {
            continue;
        }
        //分割字符串
        commandSplit(usercommand,argv);
        //执行对应的命令
        execute(argv);
    }
    return 0;
}

运行效果为:

至此,一个简易的 shell 已经初具雏形。 


<1>: 内建命令 

接下来,需要我们进行一些问题的解决。

执行 ./myshell 观察现象:

当我们输入,cd .. 和 pwd 之后,我们会发现所属路径没有变化,但现实应该是:

 

有一批命令,不能让子进程执行,必须让父进程 bash 自己执行!这些命令叫做内建命令。

内建命令就是 bash 自己执行的,类似于自己内部的一个函数!!!

在执行一个命令之前,需要知道其是否为内建命令???

对代码进行修改:

void cd(const char* path)
{
    chdir(path);
}
int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        // cd();任何实现 cd 命令??? 后面跟路径
        char* path = NULL;
        if(argv[1] == NULL)
        {
            path = ".";
        }
        else 
        {
            path = argv[1];
            cd(path);
        }
     
        return 1;
    }
    return 0;
}
int main()
{
    while(1)
    {
        char usercommand[NUM];
        char* argv[SIZE];
        int n = getUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0)
        {
            continue;
        }
        //分割字符串
        commandSplit(usercommand,argv);
        //检查是否为内建命令
        n = doBuildin(argv);
        if(n)
        {
            continue;
        }
        //执行对应的命令
        execute(argv);
    }
    return 0;
}

运行效果为:

但是此时,命令行提示符的路径没有改变 !

我们需要对环境变量进行修改,或者是获取当前环境变量。 代码为:

void cd(const char* path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));

    sprintf(cwd,"PWD=%s",tmp);
    
    putenv(cwd);
}

运行效果为:

 此时,与现实相符合。


<2>:export 的实现 

实现 export ,代码为:

int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        // cd();任何实现 cd 命令??? 后面跟路径
        char* path = NULL;
        if(argv[1] == NULL)
        {
            path = ".";
        }
        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;
}

运行效果为:

进阶 ---> 解决 if(argv[1] == NULL){path = ".";} 处的问题,代码添加:

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

int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        // cd();任何实现 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;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        char *val = argv[1]+1;//$PATH   $?
        if(strcmp(val,"?") == 0)
        {
            printf("%d\n",lastcode);//查看退出码
            lastcode = 0;
        }
        else 
        {
            printf("%s\n",getenv(val));//打印环境变量
        }
        return 1;
    }
    else 
    {}
    return 0;
}

运行效果为: 

 


<3>: echo 的实现 

 实现 echo ,代码为:

int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        // cd();任何实现 cd 命令??? 后面跟路径
        char* path = NULL;
        if(argv[1] == NULL)
        {
            path = ".";
        }
        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;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        char *val = argv[1]+1;//$PATH   $?
        if(strcmp(val,"?") == 0)
        {
            printf("%d\n",lastcode);//查看退出码
            lastcode = 0;
        }
        else 
        {
            printf("%s\n",getenv(val));//打印环境变量
        }
        return 1;
    }
    return 0;
}

运行效果为:

 echo 在打印环境变量时,获取的环境变量可能为空,即输入 echo $myval 输出 一串错误信息提示,正确情况下,如果没有定义,会进行换行操作,为解决该问题,对代码进行修改:

else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[1] == NULL)
        {
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) >1)
        {
            char *val = argv[1]+1;//$PATH   $?
            if(strcmp(val,"?") == 0)
            {
                printf("%d\n",lastcode);//查看退出码
                lastcode = 0;
            }
            else 
            {
                const char* enval = getenv(val);
                if(enval) printf("%s\n",enval);
                else printf("\n");
                //printf("%s\n",getenv(val));//打印环境变量
            }
            return 1;
        }
        else 
        {
            printf("%s\n",argv[1]);
            return 1;
        }
    }    

运行效果为:


4.可执行文件加颜色 

在 myshell 中,可执行文件不带颜色:

如果想要带颜色需要我们添加 ls -l --color:

 


5.完整代码

#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
#define SEP " "
//#define Debug 1
char cwd[1024];
char enval[1024];//测试用
int lastcode = 0;
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)
{
    char usercommand[NUM];                                                                                                        
     printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());                                                                    
    //输入                                                                                                                          
    char* tmp = fgets(command,num,stdin);                                                                       
    if(tmp == NULL)                                                                                                                 
    {                                                                                                                               
        return 1;                                                                                                                   
    }                                                                                                                               
    command[strlen(command) - 1] = '\0';                                                                                    
    return strlen(command);
}
void commandSplit(char* in,char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in,SEP);
    while(out[argc++] = strtok(NULL,SEP));
#ifdef Debug  
    for(int i = 0; out[i];i++)
    {
        printf("%d:%s\n",i,out[i]);
    }
#endif 
}
int execute(char* argv[])
{
     pid_t id = fork();
     if(id < 0)
     {
        return -1;
     }
     else if(id == 0)
     {
         //子进程 --- 程序替换
         execvp(argv[0],argv);
         exit(1);
     }
     else 
     {
         //父进程 --- 等待子进程
         int status = 0;
         pid_t rid = waitpid(id,&status,0);
         if(rid > 0)
         {
             //等待成功
             lastcode = WEXITSTATUS(status);
         }
    }
     return 0;
}
void cd(const char* path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));

    sprintf(cwd,"PWD=%s",tmp);
    
    putenv(cwd);
}
// 1 yes  0  no  -1 错误
int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        // cd();任何实现 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;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[1] == NULL)
        {
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) >1)
        {
            char *val = argv[1]+1;//$PATH   $?
            if(strcmp(val,"?") == 0)
            {
                printf("%d\n",lastcode);//查看退出码
                lastcode = 0;
            }
            else 
            {
                const char* enval = getenv(val);
                if(enval) printf("%s\n",enval);
                else printf("\n");
                //printf("%s\n",getenv(val));//打印环境变量
            }
            return 1;
        }
        else 
        {
            printf("%s\n",argv[1]);
            return 1;
        }
    }    
    else 
    {}
    return 0;
}
int main()
{
    while(1)
    {
        char usercommand[NUM];
        char* argv[SIZE];
        int n = getUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0)
        {
            continue;
        }
        //分割字符串
        commandSplit(usercommand,argv);
        //检查是否为内建命令
        n = doBuildin(argv);
        if(n)
        {
            continue;
        }
        //执行对应的命令
        execute(argv);
    }
    return 0;
}

 make 时,会有一处告警:

解决方法通过去掉 makefile 文件中的 -std=c99 进行抹除:


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值