linux(1)- 简单的 shell 解释器

      真正了不起的程序员对自己程序的每一个字节都了如指掌。
      计算机科学领域中,没有什么问题是增加一个中间层解决不了的。

做一个简单的 Shell 解释器

环境

      Ubuntu20.04 64位虚拟机

思路

      如何做一个类似的 shell 解释器,按照不用重复造轮子的思想,所以最直接的思路是:编写接口利用现成的 bash 。这是主要的思想。

具体实现目标

      1,需要做一个交互界面,能够仿照正常的终端格式打印出提示符
      2,需要处理用户的输入指令,能够对用户的命令进行处理

方法

      1,包含于头文件<pwd.h>下,在此需要用到的函数有三个,分别是 getpwuid() 获取当前用户名、gethostname() 获取主机名和getcwd() 获取当前路径。如下:


#include<pwd.h>

	struct passwd* a = getpwuid(getuid());//获得提示符
	char name[32];
	gethostname(name, 31);
	char current_dir[128];
	getcwd(current_dir, 127);
	...
	//a 是一个结构体,在此只需要访问用户名这一个成员。
	printf("%s@%s:%s$ ",a->pw_name,name,current_dir);//打印提示符


      2,每当有用户进行输入时,采用 fork()创建一个子进程,让子进程来执行具体的用户输入,执行完毕用 wait() 来回收子进程。

      执行用户输入时,在此采用 exec 函数族中的 execvp(), 这个函数原型如下:
      int execvp(const char *file, char *const argv[]);
      关于返回值:该函数执行失败则直接返回-1。
      第一个参数是要运行的脚本文件,会在环境变量PATH中查找并执行,也就是说它可以直接对用户输入进行处理(比如 ls 命令),不需要进一步地转化(将 ls 转化为 /bin/ls ),用起来较为方便。
      第二个参数是一个字符串数组,它是一个参数列表,下标为0,1,2…。注意:其中下标为0的参数是待执行的命令本身,也就是脚本文件名。
      还需要注意字符串数组最后一个参数必须为 NULL。

      举例如下:
      当用户输入命令 ls -l 时,我们所需要做的工作是:假设字符串数组名为 argv,令 argv[0]=“ls” ,令 argv[1]="-l" ,令 argv[2]=NULL 。然后采用函数调用方法为 execvp(argv[0],argv) 来执行 ls -l 命令。


      如下是子进程中的代码,用来处理用户输入:
	printf("my pid is %d,I will execute the task.\n\n", getpid());
			
	if (execvp(arg[0], arg) < 0)//执行用户命令(如ls命令:exec函数中不带p的需要采用/bin/ls形式,而带p的会自动查找环境变量。)
	{
    
    
		printf("error in execv().\n");
		exit(-1);
	}
	else
		exit(0);

完整代码

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<pwd.h>

#define SIZE 512
#define LEN 32
void getInput(char* arg[],char* input);
int main()
{
    
    
	printf("hello,It's me.\n");
	char* input=(char*)malloc(SIZE);//存储用户输入的命令
	char* arg[10];//参数列表 1+9

	char exit_p[] = "exit";		//退出处理
	char exit_input[5];
	
	
	struct passwd* a = getpwuid(getuid());//获得提示符
	char name[32];
	gethostname(name, 31);
	char current_dir[128];
	getcwd(current_dir, 127);

	while (1)
	{
    
    

		printf("%s@%s:%s$ ",a->pw_name,name,current_dir);//打印提示符

		memset(input, 0, SIZE);//每次都清0
		
		fgets(input,SIZE-1,stdin);//获得用户输入
		
		memcpy(exit_input, input, 4);//进行退出判断,是否为"exit\n"
		if(input[4]=='\n')
		{
    
    
			exit_input[4]='\0';
			if (strcmp(exit_p, exit_input) == 0)
				break;	//用户输入exit进行退出
		}

		getInput(arg, input);//将输入字符串转换为arg参数数组
		
		pid_t pid = fork();
		if (pid < 0)
			printf("error in fork().\n");
		else if (pid == 0)
		{
    
    
			printf("my pid is %d,I will execute the task.\n\n", getpid());
			
			if (execvp(arg[0], arg) < 0)//执行用户命令(如ls命令:不带p的需要采用/bin/ls形式,带p的会自动查找环境变量。)
			{
    
    
				printf("error in execv().\n");
				exit(-1);
			}
			else
				exit(0);
		}
		else
		{
    
    
			int status;
			int re = wait(&status);
			printf("\nchild process quit with status%d.\n", status);
			printf("child process pid=%d.\n", re);

			for (int i = 0; i < 10; i++)
				if (arg[i] != NULL)
					free(arg[i]);//每次都要释放内存。
		}

	}
	free(input);
	return 0;
	
}
void getInput(char* arg[10], char* input)
{
    
    
	for (int i = 0; i < 10; i++)
		arg[i] = NULL;	//保证最后一个参数始终为NULL
	if (input[0] == '\n')
		return;
	int i = 0,flag = 1;//i 作为input的下标扫描输入字符. flag 用于多个空格符的检测以及是否使用 malloc()
	int j,k = -1;//k 作为参数的下标,最大为9. j为每个参数中字符的下标,最大为31
	while (input[i] != '\n')
	{
    
    
		if (input[i] == ' ')
			flag = 1;
		else
		{
    
    
			if(flag)
			{
    
    
				flag = 0;
				k++;
				arg[k] = (char*)malloc(LEN);
				memset(arg[k], 0, LEN); //保证每个可用参数最后为'\0'
				j = 0;
			}
			arg[k][j++] = input[i];

		}
		i++;
	}
	return;
}

功能

      能够实现 ls,ps,touch,rm等命令,不能实现 cd 命令,只能在当前目录下进行操作。
      输入 exit 退出。

运行截图

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Little_ant_/article/details/112766749
今日推荐