编写一个简单的shell

编写一个简单的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循环即可。

  

上面代码还有不足之处,请大家多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值