一.shell的原理
Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序。它不属于内核部分,而是在核心之外,以用户态方式运行。
其基本功能是解释并执行用户打入的各种命令,实现用户与Linux核心的接口。系统初启后,核心为每个终端用户建立一个进程去
执行Shell解释程序,shell的简单定义就是命令行解释器。
我现在打印一下当前进程的父进程。
编译运行后,打印出来的就是父进程,通过ps aux来查看该父进程的状态。我们可以很明显的看到当前test对应的进程------56042进程就是bash进程,而bash就是一个具体的shell。shell会f先ork一个子进程,它最后会被替换成我们敲入指令对应的代码和数据。
二.整体思路
- 打印一个提示符,让用户输入一个指令。
- 解析输入的指令,找到对应的可执行程序。
- 创建子进程,子进程程序替换(替换成你敲入的命令),来加载可执行程序。
- 父进程进行进程等待,等待子进程结束。
- 子进程结束,父进程从wait中返回,循环执行1.
1.打印一个提示符,让用户输入一个指令。
我们知道每次输入指令的时候,都会有一个提示符,我在这里为了简便暂时将这个提示符写死,不随着用户所在路径而变化。这个部分非常的简单,一个简单的printf就可以解决问题。
printf("[zhaotiedan@localhost myshell]$");
2. 解析输入的指令,找到对应的可执行程序。
首先要读取用户输入的指令,我一开始使用的输入函数是scanf,但是它是一个遇到空格就返回的函数,这就代表假如我输入的是ls -l指令,那么这个函数就会以分两次返回,一次返回ls,一次返回-l。所以我在这里选用了gets函数,一次读一行数据。
char *strtok(char str[], const char *delim);
//str是要分解的字符串,delim是字符串中用来分解的字符
我在这里来分装一个方法Split,在里面使用strtok这个函数,将切分出来的结果依次放入一个字符串数组里面,在main函数里只需拿一个返回值来接收就ok了。
int Split(char input[],char* output[])
//input表示待切分的命令
//output表示切分结果
{
char* p=strtok(input," ");//用空格来分解input
int i=0;
while(p!=NULL)
{
output[i]=p;
i++;
p=strtok(NULL," ");
}
output[i]=NULL;
return 0;
}
char *argv[1024]={0};
int n=Split(command,argv);
这个时候我就能到达这样的效果了
那我们现在再详细的剖析一下strtok函数的执行过程。
分析得到,第一次调用strtok时,strtok返回的是指向ls(待切分指令的第一个字符串)的指针,当打印时,本来ls后面应该打印空格,但是我们可以很明显的看到是因为读取到‘\0’才调用结束的。那么,可以得出一个结论,是strtok将字符串的最后一个空格替换成了’\0’,所以strtok函数具有破坏原始字符串的功能。
第二次调用strtok时,strtok返回的是指向-的指针,而后面都会从上次切分的位置开始往后切分,所以strtok可以保存上次的切分结果。
第三次调用时,strtok返回指向/的指针
最后一次调用,srtok返回NULL;
总结:strtok必须循环调用,并且第一次调用和后续调用时传的参数是不一样的。并且使用的时候会导致线程不安全
strtok调用难,而且调用风险这么大,所以最科学的方法是基于boost的方法。Boost是为C++语言标准库提供扩展的一些C++程序库的总称,它是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称。像C++中的智能指针就是基于这个库来使用的。
3-5创建子进程,子进程程序替换,来加载可执行程序。
void CreateProcess(char* argv[],int n)
{
(void)n;
//1.创建子进程
int ret=fork();
//2.父进程进行进程等待,子进程进行程序替换
if(ret>0)
{
//father
wait(NULL);
}
else if(ret==0)
{
//child
ret=execvp(argv[0],argv);//敲入的指令只有一个名字,没有路径,得去path中找
//出错的判定if可省略,如果替换成功,肯定不会再执行后面这些代码
perror("exec");
exit(0);
}
else{
perror("fork");
}
}
三.源代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
//input--待切分命令
//output--表示切分u结果(字符串数组)
//返回值为数组中有效的指令个数
int Split(char input[],char* output[])
{
//借助strtok
char* p=strtok(input," ");//以空格来切分,返回的是指向第一个字符串的指针
int i=0;
while(p!=NULL)
{
//字符串放入output
output[i]=p;
i++;
p=strtok(NULL," ");
}
output[i]=NULL;//必须以空指针来结尾
return i;
}
void CreateProcess(char* argv[],int n)
{
(void)n;
//1.创建子进程
int ret=fork();
//2.父进程进行进程等待,子进程进行程序替换
if(ret>0)
{
//father
wait(NULL);
}
else if(ret==0)
{
//child
ret=execvp(argv[0],argv);//敲入的指令只有一个名字,没有路径,得去path中找
//出错的判定if可省略,如果替换成功,肯定不会再执行后面这些代码
perror("exec");
exit(0);
}
else{
perror("fork");
}
}
int main()
{
while(1)
{
//1.打印一个提示符,用缓冲区刷新输出到显示器上
printf("[zhaotiedan@localhost myshell]$");
//fflush(stdout);
//2.用户输入一个指令,遇到回车读取结束
char command[1024]={0};
//scanf("%s",command);//scanf遇到空格会返回,所以不能用它
gets(command);//一次读一行数据
//3.解析指令,把要执行哪个程序识别出来,哪些是命令,哪些是参数
char *argv[1024]={0};//放切分结果
int n=Split(command,argv);
//4.创建子进程,进行程序替换
CreateProcess(argv,n);
}
return 0;
}
四 .功能展示
- ls
- ls -l
四.缺陷和改进点
- 要做到提示符随用户当前所在路径变换而变化。
- 要支持cd命令,因为cd修改的是子进程的路径,对父进程没有影响。需要让父进程直接支持cd(而不是创建子进程和程序替换)
- 需要支持定义别名。
- 需要支持管道。
- 需要支持重定向。