Linux:简单自定义shell

1.实现原理

考虑下⾯这个与shell典型的互动:

cpp 复制代码
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps

⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运⾏ls程序并等待那个进程结束。


然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序 并等待这个进程结束。
所以要写⼀个shell,需要循环以下过程:

  1. 获取命令⾏
  2. 解析命令⾏
  3. 建⽴⼀个⼦进程(fork)
  4. 替换⼦进程(execvp)
  5. ⽗进程等待⼦进程退出(wait)

2.实现代码

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]"

//shell定义的全局数据

//1.命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;

//2.环境变量表
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;

char cwd[1024];
char cwdenv[2048];

//last exit code
int lastcode = 0;

const char* GetUserName()
{
	const char* name = getenv("USER");
	return name == NULL ? "None" : name;
}

const char* GetHostName()
{
	const char* hostname = getenv("HOSTNAME");
	return hostname == NULL ? "None" : hostname;
}

const char* GetPwd()
{
	const char* pwd = getcwd(cwd, sizeof(cwd));
	if (pwd != NULL)
	{
		snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
		putenv(cwdenv);
	}
	return pwd == NULL ? "None" : pwd;
}

const char* GetHome()
{
	const char* home = getenv("HOME");
	return home == NULL ? "" : home;
}

void InitEnv()
{
	extern char** environ;
	memset(g_env, 0, sizeof(g_env));
	g_envs = 0;

	//从配置文件中获取环境变量
	for (int i = 0; environ[i]; i++)
	{
		g_env[i] = (char*)malloc((strlen(environ[i]) + 1));
		strcpy(g_env[i], environ[i]);
		g_envs++;
	}

	//测试
	g_env[g_envs++] = (char*)"HAHA=for_test";
	g_env[g_envs] = NULL;

	//导成环境变量
	for (int i = 0; g_env[i]; i++)
	{
		putenv(g_env[i]);
	}
	environ = g_env;
}

bool Cd()
{
	if (g_argc == 1)
	{
		std::string home = GetHome();
		if (home.empty())return true;
		chdir(home.c_str());
	}
	else
	{
		std::string where = g_argv[1];
		// cd - / cd ~
		if (where == "-")
		{
			// Todu
		}
		else if (where == "~")
		{
			// Todu
		}
		else
		{
			chdir(where.c_str());
		}
	}
	return true;
}

void Echo()
{
	if (g_argc == 2)
	{
		std::string opt = g_argv[1];
		if (opt == "$?")
		{
			std::cout << lastcode << std::endl;
			lastcode = 0;
		}
		else if (opt[0] == '$')
		{
			std::string env_name = opt.substr(1);
			const char* env_value = getenv(env_name.c_str());
			if (env_value)
				std::cout << env_value << std::endl;
		}
		else
		{
			std::cout << opt << std::endl;
		}
	}
}

std::string DirName(const char* pwd)
{
#define SLASH "/"
	std::string dir = pwd;
	if (dir == SLASH)return SLASH;
	auto pos = dir.rfind(SLASH);
	if (pos == std::string::npos)return "BUG?";
	return dir.substr(pos + 1);
}

void MakeCommandLine(char cmd_prompt[], int size)
{
	snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}

void PrintCommandPrompt()
{
	char prompt[COMMAND_SIZE];
	MakeCommandLine(prompt, sizeof(prompt));
	printf("%s", prompt);
	fflush(stdout);
}

bool GetCommandLine(char* out, int size)
{
	//ls -a -l ->"ls -a -l"字符串
	char* c = fgets(out, size, stdin);
	if (c == NULL)return false;//fgets失败
	out[strlen(out) - 1] = 0;//清理\n
	if (strlen(out) == 0)return false;//如只输入\n
	return true;
}

bool CommandParse(char* commandline)
{
#define SEP " "
	g_argc = 0;
	//命令行分析"ls -a -l"->"ls" "-a" "-l"  
	g_argv[g_argc++] = strtok(commandline, SEP);
	while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
	g_argc--;
	return g_argc > 0 ? true : false;

}

bool CheckAndExecBuildin()
{
	std::string cmd = g_argv[0];
	if (cmd == "cd")
	{
		Cd();
		return true;
	}
	else if (cmd == "echo")
	{
		Echo();
		return true;
	}
	//else if(cmd == "export")
	//else if(cmd == "alias")
	//...
	return false;
}

int Execute()
{
	pid_t id = fork();
	if (id == 0)
	{
		execvp(g_argv[0], g_argv);
		exit(1);
	}
	int status = 0;
	pid_t rid = waitpid(id, &status, 0);
	if (rid > 0)
	{
		lastcode = WEXITSTATUS(status);
	}
	return 0;
}

int main()
{
	// shell 启动的时候,从系统中获取环境变量
	// 我们的环境变量信息应该从父shell统一来

	InitEnv();

	while (true)
	{
		//1.输出命令行提示符
		PrintCommandPrompt();

		//2.输入用户输入的命令
		char commandline[COMMAND_SIZE];
		if (!GetCommandLine(commandline, sizeof(commandline)))
			continue;

		//3.命令行分析"ls -a -l"->"ls" "-a" "-l"
		if (!CommandParse(commandline))
			continue;

		//4.检测并处理内建命令
		if (CheckAndExecBuildin())
			continue;

		//5.执行命令
		Execute();

	}
	return 0;
}
相关推荐
HLC++8 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室9 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy11 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕12 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫13 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生14 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1115 小时前
linux基础IO
linux·运维·服务器
BelongPanda17 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯17 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983417 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器