手把手带你写一个微型Shell

PS:更新的这个版本支持重定向功能。

首先,我们来理理思路。主要的思路就是用当前进程(我们写的shell程序)创建一个子进程,然后让exec函数族中的函数用我们指定的进程映像(我们在终端输入的命令就是调用相应的进程)替换掉子进程的进程映像,子进程执行完毕后,用wait函数来接收子进程(避免产生僵尸进程),然后这个过程循环起来,就实现了一个连续的命令解释程序(my_shell)。我画一个图,方便大家理解这个过程。

流程图

以上就是我们的思路了,下面我们来开始写代码吧。。。

  • 首先,我们把main函数的代码写一下,为了能够让代码清楚一点,我把复杂的代码单独拿出来讲解
int main( void )
{
    char infor[1024]={0};//用来接收我们输入的命令
    while(1)
    {
        printf("my@localhost:>>>");
        scanf("%[^\n]%*c",infor);
        m_Inplement(infor);//调用底层执行命令的函数
    }
}

printf(“my@localhost:>>>”);

这句代码printf中你可以随意输出,只要你愿意,甚至还可以和原来的终端提示符一模一样

scanf(“%[^\n]%*c”,infor);

这个算是scanf的高级用法了吧。。。[]内部是说第一个%只接受[]内部的格式输入,^\n的意思是接收换行符以前的所有输入,输入到infor中,%*c的意思是最后再接收一个字符,也就是前面遗留下来的换行符,为什么要有一个星号呢?这是说接收并且丢弃掉这个字符,因为我们不需要将换行符放到数组infor中去。


  • 然后我们来实现执行命令的函数 m_Inplement()
void m_Inplement(char *infor)
{
    int argc=0;
    int *argv[8];
    int state=0;
    int i=0;
    for(;infor[i] != 0; i++)
    {
        if(!isspace(infor[i]) && state ==0)
        {
            argv[argc++]=infor+i;
            state=0;
        }
        else if(isspace(infor[i]))
        {
            infor[i]=0;
            state=0;
        }
    }
    argv[argc]=NULL;
    do_shell(argc,argv);
}

m_Inplement()函数主要做了两件事,第一件事,解析从终端输入的字符,并将之放到类似于main函数第二个参数的锯齿数组中,以便后面对命令进行操作。


那么首先,我们来看第一件事
1.字符串解析的思路: 采用有限状态机的思想,我们在这里定义存放终端字符的数组的两种状态,一种是处于无效字符,我们用state=0来表示这种状态;一种是处于有效字符,我们要对其字符进行解析和存放,我们用state=1来表示这种状态。
- 如果当前字符不为空白符,并且上一种状态是0,那么我们就进入字符解析模式

  • 如果当前字符为空白符,那么当前就处于无效模式

2.代码解析:(以下将存放终端字符的数组称为缓冲数组,将存放命令的数组称为命令行数组)

int *argv[8];

这里我们就实现一个简易的方案,指定每次输入的命令最多只能是八个,真正的shell这里是可以无限输入的

isspace()

这个函数用来判断当前字符是否为空白符,是则返回1,否则返回0.包含在头文件< string.h>下

argv[argc++]=infor + i;

将缓冲数组中的当前命令的起始地址存放到命令数组的指定位置,然后让argc++(argc是命令的个数),这行代码也可以这样写:argv[argc++]=&infor[i];

argv[argc]=NULL;

出了上面的那个循环,说明命令已经全部放到了命令数组,然后让命令数组以NULL结尾

do_shell(argc,argv);

最后让最底层的执行命令的函数去执行终端指定的命令即可(argc是命令的个数,argv是命令)


注意:这个函数我在写的时候犯了一个很严重的低级错误,就是在定义state和argc变量的时候没有初始化,导致后面一直没有出现预知的效果!


  • 最后来写指do_shell()函数

这里支持重定向功能,那么重定向功能是怎么实现的呢?首先你得了解dup2()这d个函数

函数原型:
int dup2(int oldfd, int newfd);
参数:
第一个参数是旧的文件描述符,也就是需要替换的文件描述符;
第二个参数是新的文件描述符。

函数说明:

void do_shell(int argc,char *argv[])
{
    pid_t pid =fork();//创建一个子进程
    if(pid == -1)//创建失败的情况处理
    {
        perror("fork");
        exit(1);
    }
    else if(pid == 0)//如果是子进程
    {
        int tag = 0;
        int i = 0;
        for(; argv[i] != NULL; i++)
        {
            if(strcmp(">", argv[i]) == 0)
            {
                tag = 1;
                break;
            }
            argv[i] = NULL;
            int newfd;
            if(tag)
            {
                if(NULL == argv[i + 1];
                {
                    printf("commend error!\n");
                    exit(1);
                }
                close(1);
                int fd = open(argv[i + 1],O_WRONLY | O_CREAT,644);
                newfd = dup2(1, fd);
            }
            execvp(argv[0], argv);
            if(tag)
            {
                close(1);
                dup2(newfd, 1);
            }
            exit(1);
        }
    }
    else 
        wait(NULL);
}

到这里这个简易版的shell就写好了,是不是很简单呢?
这里还有一个小问题,这个my_shell怎么退出呢?exit? 是不是发现这样不能退出?
当然不能,因为真正的shell可以处理内置命令和外置命令,而我们写的这个只能简单的处理外置命令,并没有做内置命令的处理,所以这个my_shell只能用ctrl + c的方式来结束这个进程。
最后我在加一段代码,内置一条exit命令来结束my_shell,也顺便做一个内置命令的实例:

//用下面这段代码替换掉上面的main函数
#include<string.h>//main函数中添加代码要用到的头文件
int main( void )
{
    char infor[1024]={0};
    while(1)
    {
        printf("my@localhost:>>>");
        scanf("%[^\n]%*c",infor);
        if(strncmp(infor,"exit",4) == 0)
            exit(0);
        m_Inplement(infor);
    }
}

PS:这是作者的脑力劳动成果,转载请注明出处:
https://blog.csdn.net/zanda_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值