编写一个简单的shell:今天只是写个框架,后面会完善shell ,注:暂且不支持内建命令,不支持重定向,不支持管道
先介绍一下shell:Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序。它不属于内核部分,而是在核心之外,是操作系统的外壳程序,以用户态方式运行。其基本功能是解释并执行用户打入的各种命令,实现用户与Linux核心的接口。系统初启后,核心为每个终端用户建立一个进程去执行Shell解释程序。
shell的执行过程:
(1)读取用户由键盘输入的命令行。
(2)分析命令,以命令名作为文件名,并将其它参数改造为系统调用execve( )内部处理所要求的形式。
(3)终端进程调用fork( )建立一个子进程。
(4)终端进程本身用系统调用wait4( )来等待子进程完成(如果是后台命令,则不等待)。当子进程运行时调用execve(),子进程根据文件名(即命令名)到目录中查找有关文件(这是命令解释程序构成的文件),将它调入内存,执行这个程序(解释这条命令)。
(5)如果命令末尾有&号(后台命令符号),则终端进程不用系统调用wait4( )等待,立即发提示符,让用户输入下一个命令,转⑴。如果命令末尾没有&号,则终端进程要一直等待,当子进程(即运行命令的进程)完成处理后终止,向父进程(终端进程)报告,此时终端进程醒来,在做必要的判别等工作后,终端进程发提示符,让用户输入新的命令,重复上述处理过程。
具体实现代码:
(1)为了模仿系统的shell,先把名字,主机名,当前路径,打印出来
1) 先介绍两个获取用户名的函数:
getuid函数:函数返回一个调用程序的真实用户ID,一般来说,这个函数都是会调用成功。
函数原型:uid_t getuid(void);
函数头文件:#include <unistd.h> #include <sys/types.h>
getpwuid函数:根据用户id,把用户信息存储在一个结构体中
函数原型:struct passwd *getpwuid(uid_t uid);
函数头文件: #include <sys/types.h> #include <pwd.h>
现在贴代码 获取用户名:
void GetLogName()
{
struct passwd* pass;
pass = getpwuid(getuid());
printf("[%s@",pass->pw_name);
}
2)获取主机名
gethostname函数:
函数原型: int gethostname(char *name, size_t len);
函数头文件:#include <unistd.h>
函数参数:name保存获取到信息,len是期望获取字符个数
void GetHostName()
{
char name[128];
gethostname(name,sizeof(name)-1);
printf("%s",name);
}
3)获取当前路径
getcwd函数:
函数原型:char *getcwd(char *buf, size_t size);
函数参数:获取当前路径保存到buf中
函数返回值:成功返回存储路径的指针,失败NULL。
void GetDir()
{
char pwd[128];
getcwd(pwd,sizeof(pwd)-1);
int len = strlen(pwd);
char* p = pwd+len-1;
while(*p != '/')
{
p--;
}
p++;
printf(" %s]@",p);
}
注意:执行完这三个函数必须用fflush(stdout) 刷新缓存区;
(2)从键盘读取命令,存取到cmd数组中,然后把命令分解存放到_argv指针数组中
read函数:从文件标识符fd中读取count个字符到buf中
函数原型: ssize_t read(int fd, void *buf, size_t count);
直接贴代码解释吧:
char cmd[128]; //存放命令
ssize_t _s = read(0, cmd, sizeof(cmd)-1);// 从标准输入流0 读取命令
if(_s > 0)//read success, _s is read number
{
cmd[_s-1] = '\0'; //由于最后读取一个回车\n
}
else
{
perror("read");
return 1;
}
char* _argv[32]; // 指针数组 存放cmd命令,为execvp函数
_argv[0] = cmd;
char* start = cmd;// start作为一个循环标志
int i =1;
while(*start)
{
if(isspace(*start))
{
*start ='\0';
start++;
_argv[i] = start;
i++;
continue;
}
start++;
}
_argv[i] = NULL;
(3)终端进程调用fork函数创建子进程,子进程调用execvp函数进行解释命令
execvp函数不在解释,前面博客有介绍,
说下获取程序退出的状态的宏
WIFEXITED:这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
WEIXTSTATUS:获得子进程exit()返回的结束代码,一般先要用WIFEXITED判断是否正常结束
贴代码:
pid_t id =fork();
if(id <0)
{
perror("fork failed\n");
}else if(id == 0) //child -> run cmd
{
execvp(_argv[0],_argv);
exit(1);
}
else //father
{
int status =0;
pid_t ret = waitpid(id,&status,0);//等待子进程执行完
if(ret > 0 && WIFEXITED(status))
{
printf("exit code: %d\n",WEXITSTATUS(status)); //获取退出码
}
else
{
perror("wait failed");
}
}
程序测试结果:
现在就已经简单的实现了shell,但是有一个不足之处就是只能执行一次,这个问题很好解决:只要在外层加一个while循环即可。
上面代码还有不足之处,请大家多多指教。