简单的 shell 程序
整体思路
一个简单的 shell 程序的工作流程如下:
- 初始化环境:在启动时从系统获取环境变量。
- 循环等待用户输入:不断输出命令行提示符,等待用户输入命令。
- 解析命令:把用户输入的命令解析成可执行的格式。
- 执行命令:判断是内置命令还是外部命令,然后执行相应操作。
1. 定义全局变量
在编写一个简单的 shell 程序时,我们需要一些全局变量来存储和管理不同类型的数据。以下是详细解释:
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "// 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0; // 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;// 别名映射表
std::unordered_map<std::string, std::string> alias_list;// 最后一次命令的退出码
int lastcode = 0;
COMMAND_SIZE
:这是一个宏定义,用来规定用户输入命令行的最大长度。在读取用户输入时,我们可以使用这个值来确保不会超出缓冲区的范围,避免缓冲区溢出问题。FORMAT
:同样是宏定义,它规定了命令行提示符的格式。%s
是格式化字符串中的占位符,分别用于插入用户名、主机名和当前工作目录。MAXARGC
:定义了命令行参数的最大数量。g_argv
是一个字符指针数组,用于存储解析后的命令行参数。g_argc
则记录当前命令行参数的实际数量,初始化为 0。MAX_ENVS
:规定了环境变量表的最大容量。g_env
是一个字符指针数组,用于存储环境变量的字符串。g_envs
记录当前环境变量的实际数量,初始化为 0。alias_list
:这是一个std::unordered_map
,用于存储命令别名和实际命令的映射关系。用户可以通过alias
命令为常用命令设置别名,方便使用。lastcode
:用于存储上一次执行命令的退出码。退出码可以表示命令执行的结果,例如 0 通常表示成功,非 0 表示有错误发生。
2. 实现获取用户信息的函数
这些函数用于获取一些系统信息,用于显示在命令行提示符中或在某些操作中使用。
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;
}
GetUserName()
:使用getenv
函数从环境变量中获取当前用户的用户名。如果获取失败(返回NULL
),则返回"None"
。GetHostName()
:同样使用getenv
函数获取主机名。若获取失败,返回"None"
。GetPwd()
:getcwd
函数用于获取当前工作目录的路径,并将其存储在cwd
数组中。如果获取成功,使用snprintf
函数将路径格式化为PWD=路径
的形式,存储在cwdenv
中,然后使用putenv
函数将其设置为环境变量。最后返回当前工作目录的路径,如果获取失败则返回"None"
。GetHome()
:使用getenv
函数获取用户的主目录路径。如果获取失败,返回空字符串。
3. 初始化环境变量
在 shell 启动时,需要从系统中获取环境变量,并将其存储到自定义的环境变量表中。
c++
void InitEnv()
{extern char **environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;// 获取环境变量for(int i = 0; environ[i]; i++){// 1.1 申请空间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"; // for_testg_env[g_envs] = NULL;// 导入环境变量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}
extern char **environ;
:environ
是一个全局变量,指向系统的环境变量数组。通过extern
声明,我们可以在当前函数中使用它。memset(g_env, 0, sizeof(g_env));
:将g_env
数组初始化为全 0,确保每个元素都为空指针。g_envs = 0;
:将环境变量的实际数量初始化为 0。- 获取环境变量:使用
for
循环遍历系统的环境变量数组environ
,为每个环境变量分配内存空间,并将其内容复制到g_env
数组中。同时,增加g_envs
的值来记录环境变量的数量。 g_env[g_envs++] = (char*)"HAHA=for_test";
:添加一个测试用的环境变量"HAHA=for_test"
。g_env[g_envs] = NULL;
:确保环境变量数组以NULL
结尾,这是符合环境变量数组的标准格式。- 导入环境变量:使用
putenv
函数将g_env
数组中的环境变量设置到系统中。最后,将environ
指向g_env
,使得后续的操作都使用自定义的环境变量表。
4. 实现内置命令处理函数
内置命令是 shell 本身提供的命令,不需要创建新的进程来执行。以下是几个常见内置命令的处理函数。
Cd
命令
c++
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];if(where == "-"){const char *old_pwd = getenv("OLDPWD");if (old_pwd){chdir(old_pwd);setenv("OLDPWD", getcwd(cwd, sizeof(cwd)), 1);}}else if(where == "~"){std::string home = GetHome();if (!home.empty()){chdir(home.c_str());}}else{chdir(where.c_str());}}return true;
}
g_argc == 1
:如果cd
命令后面没有参数,使用GetHome()
函数获取用户的主目录,并使用chdir
函数将当前工作目录切换到主目录。where == "-"
:如果参数是-
,表示切换到上一个工作目录。使用getenv
函数获取OLDPWD
环境变量的值,然后使用chdir
函数切换到该目录。同时,使用setenv
函数更新OLDPWD
环境变量为当前工作目录。where == "~"
:如果参数是~
,表示切换到用户的主目录。使用GetHome()
函数获取主目录路径,然后使用chdir
函数进行切换。- 其他情况:直接使用
chdir
函数将当前工作目录切换到指定的路径。
Echo
命令
c++
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 << "Environment variable not found." << std::endl;}}else{std::cout << opt << std::endl;}}
}
opt == "$?"
:如果参数是$?
,表示输出上一次命令的退出码。将lastcode
的值输出到控制台,并将lastcode
重置为 0。opt[0] == '$'
:如果参数以$
开头,表示输出对应的环境变量的值。使用substr
函数截取$
后面的部分作为环境变量名,然后使用getenv
函数获取该环境变量的值。如果找到则输出,否则输出错误信息。- 其他情况:直接将参数输出到控制台。
5. 实现命令行处理函数
这些函数用于处理命令行的显示、输入和解析。
生成并输出命令行提示符
c++
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);
}
MakeCommandLine
:使用snprintf
函数将用户名、主机名和当前工作目录的简短名称(通过DirName
函数获取)按照FORMAT
格式填充到cmd_prompt
数组中。PrintCommandPrompt
:创建一个prompt
数组,调用MakeCommandLine
函数生成命令行提示符,然后使用printf
函数输出到控制台。最后使用fflush(stdout)
强制刷新输出缓冲区,确保提示符立即显示。
获取用户输入的命令
c++
bool GetCommandLine(char *out, int size)
{char *c = fgets(out, size, stdin);if(c == NULL) return false;out[strlen(out) - 1] = 0; // 清理\nif(strlen(out) == 0) return false;return true;
}
- 使用
fgets
函数从标准输入(键盘)读取一行命令,存储到out
数组中。 - 如果
fgets
返回NULL
,表示读取失败,返回false
。 out[strlen(out) - 1] = 0;
:fgets
会将换行符\n
也读取到字符串中,这里将其替换为字符串结束符\0
,去除换行符。- 如果读取的字符串长度为 0,说明用户没有输入有效内容,返回
false
。 - 否则返回
true
,表示成功获取到命令。
解析命令行
c++
bool CommandParse(char *commandline)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(commandline, SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--;return g_argc > 0? true:false;
}
SEP
是一个宏定义,表示命令行参数的分隔符,这里使用空格。g_argc = 0;
:将命令行参数的实际数量初始化为 0。g_argv[g_argc++] = strtok(commandline, SEP);
:使用strtok
函数将commandline
字符串按照分隔符SEP
进行分割,获取第一个参数,并存储到g_argv
数组中。同时增加g_argc
的值。while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
:使用strtok
函数继续分割剩余的字符串,直到没有更多的参数为止。每次分割得到的参数都存储到g_argv
数组中,并增加g_argc
的值。g_argc--;
:由于最后一次分割会得到一个NULL
指针,所以需要将g_argc
的值减 1。- 如果
g_argc
大于 0,表示解析到了有效的命令行参数,返回true
;否则返回false
。
6. 检测并执行内置命令
c++
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export"){if (g_argc == 2){std::string var = g_argv[1];char *equal_pos = strchr(var.c_str(), '=');if (equal_pos){*equal_pos = '\0';setenv(var.c_str(), equal_pos + 1, 1);}}return true;}else if(cmd == "alias"){if (g_argc == 3){std::string nickname = g_argv[1];std::string real_cmd = g_argv[2];alias_list[nickname] = real_cmd;}return true;}return false;
}
std::string cmd = g_argv[0];
:获取命令行的第一个参数,即命令名。cmd == "cd"
:如果命令是cd
,调用Cd
函数执行cd
命令,并返回true
。cmd == "echo"
:如果命令是echo
,调用Echo
函数执行echo
命令,并返回true
。cmd == "export"
:如果命令是export
,并且参数数量为 2,使用strchr
函数查找参数中=
的位置。如果找到,将=
替换为字符串结束符\0
,然后使用setenv
函数设置环境变量。最后返回true
。cmd == "alias"
:如果命令是alias
,并且参数数量为 3,将第一个参数作为别名,第二个参数作为实际命令,存储到alias_list
中。最后返回true
。- 如果以上条件都不满足,返回
false
,表示该命令不是内置命令。
7. 执行外部命令
c++
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;
}
pid_t id = fork();
:使用fork
函数创建一个新的进程。fork
函数会返回两次,在父进程中返回子进程的进程 ID,在子进程中返回 0。- 子进程:如果
id == 0
,表示当前是子进程。使用execvp
函数执行命令行指定的外部命令。execvp
函数会用新的程序替换当前进程的映像,如果执行失败,会返回 -1。为了避免子进程继续执行后面的代码,使用exit(1)
终止子进程。 - 父进程:在父进程中,使用
waitpid
函数等待子进程结束。waitpid
函数会阻塞父进程,直到指定的子进程结束。status
参数用于存储子进程的退出状态。 lastcode = WEXITSTATUS(status);
:使用WEXITSTATUS
宏从status
中提取子进程的退出码,并将其存储到lastcode
中。- 最后返回 0,表示执行成功。
8. 主函数
c++
int main()
{// 初始化环境变量InitEnv();while(true){// 输出命令行提示符PrintCommandPrompt();// 获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 解析命令行if(!CommandParse(commandline))continue;// 检测并执行内置命令if(CheckAndExecBuiltin())continue;// 执行外部命令Execute();}// 释放内存for (int i = 0; i < g_envs; ++i){free(g_env[i]);}return 0;
}
1. 定义全局变量
在编写一个简单的 shell 程序时,我们需要一些全局变量来存储和管理不同类型的数据。以下是详细解释:
c++
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "// 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0; // 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;// 别名映射表
std::unordered_map<std::string, std::string> alias_list;// 最后一次命令的退出码
int lastcode = 0;
COMMAND_SIZE
:这是一个宏定义,用来规定用户输入命令行的最大长度。在读取用户输入时,我们可以使用这个值来确保不会超出缓冲区的范围,避免缓冲区溢出问题。FORMAT
:同样是宏定义,它规定了命令行提示符的格式。%s
是格式化字符串中的占位符,分别用于插入用户名、主机名和当前工作目录。MAXARGC
:定义了命令行参数的最大数量。g_argv
是一个字符指针数组,用于存储解析后的命令行参数。g_argc
则记录当前命令行参数的实际数量,初始化为 0。MAX_ENVS
:规定了环境变量表的最大容量。g_env
是一个字符指针数组,用于存储环境变量的字符串。g_envs
记录当前环境变量的实际数量,初始化为 0。alias_list
:这是一个std::unordered_map
,用于存储命令别名和实际命令的映射关系。用户可以通过alias
命令为常用命令设置别名,方便使用。lastcode
:用于存储上一次执行命令的退出码。退出码可以表示命令执行的结果,例如 0 通常表示成功,非 0 表示有错误发生。
2. 实现获取用户信息的函数
这些函数用于获取一些系统信息,用于显示在命令行提示符中或在某些操作中使用
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;
}
GetUserName()
:使用getenv
函数从环境变量中获取当前用户的用户名。如果获取失败(返回NULL
),则返回"None"
。GetHostName()
:同样使用getenv
函数获取主机名。若获取失败,返回"None"
。GetPwd()
:getcwd
函数用于获取当前工作目录的路径,并将其存储在cwd
数组中。如果获取成功,使用snprintf
函数将路径格式化为PWD=路径
的形式,存储在cwdenv
中,然后使用putenv
函数将其设置为环境变量。最后返回当前工作目录的路径,如果获取失败则返回"None"
。GetHome()
:使用getenv
函数获取用户的主目录路径。如果获取失败,返回空字符串。
3. 初始化环境变量
在 shell 启动时,需要从系统中获取环境变量,并将其存储到自定义的环境变量表中。
void InitEnv()
{extern char **environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;// 获取环境变量for(int i = 0; environ[i]; i++){// 1.1 申请空间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"; // for_testg_env[g_envs] = NULL;// 导入环境变量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}
extern char **environ;
:environ
是一个全局变量,指向系统的环境变量数组。通过extern
声明,我们可以在当前函数中使用它。memset(g_env, 0, sizeof(g_env));
:将g_env
数组初始化为全 0,确保每个元素都为空指针。g_envs = 0;
:将环境变量的实际数量初始化为 0。- 获取环境变量:使用
for
循环遍历系统的环境变量数组environ
,为每个环境变量分配内存空间,并将其内容复制到g_env
数组中。同时,增加g_envs
的值来记录环境变量的数量。 g_env[g_envs++] = (char*)"HAHA=for_test";
:添加一个测试用的环境变量"HAHA=for_test"
。g_env[g_envs] = NULL;
:确保环境变量数组以NULL
结尾,这是符合环境变量数组的标准格式。- 导入环境变量:使用
putenv
函数将g_env
数组中的环境变量设置到系统中。最后,将environ
指向g_env
,使得后续的操作都使用自定义的环境变量表。
4. 实现内置命令处理函数
内置命令是 shell 本身提供的命令,不需要创建新的进程来执行。以下是几个常见内置命令的处理函数。
Cd
命令
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];if(where == "-"){const char *old_pwd = getenv("OLDPWD");if (old_pwd){chdir(old_pwd);setenv("OLDPWD", getcwd(cwd, sizeof(cwd)), 1);}}else if(where == "~"){std::string home = GetHome();if (!home.empty()){chdir(home.c_str());}}else{chdir(where.c_str());}}return true;
}
g_argc == 1
:如果cd
命令后面没有参数,使用GetHome()
函数获取用户的主目录,并使用chdir
函数将当前工作目录切换到主目录。where == "-"
:如果参数是-
,表示切换到上一个工作目录。使用getenv
函数获取OLDPWD
环境变量的值,然后使用chdir
函数切换到该目录。同时,使用setenv
函数更新OLDPWD
环境变量为当前工作目录。where == "~"
:如果参数是~
,表示切换到用户的主目录。使用GetHome()
函数获取主目录路径,然后使用chdir
函数进行切换。- 其他情况:直接使用
chdir
函数将当前工作目录切换到指定的路径。
Echo
命令
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 << "Environment variable not found." << std::endl;}}else{std::cout << opt << std::endl;}}
}
opt == "$?"
:如果参数是$?
,表示输出上一次命令的退出码。将lastcode
的值输出到控制台,并将lastcode
重置为 0。opt[0] == '$'
:如果参数以$
开头,表示输出对应的环境变量的值。使用substr
函数截取$
后面的部分作为环境变量名,然后使用getenv
函数获取该环境变量的值。如果找到则输出,否则输出错误信息。- 其他情况:直接将参数输出到控制台。
5. 实现命令行处理函数
这些函数用于处理命令行的显示、输入和解析。
生成并输出命令行提示符
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);
}
MakeCommandLine
:使用snprintf
函数将用户名、主机名和当前工作目录的简短名称(通过DirName
函数获取)按照FORMAT
格式填充到cmd_prompt
数组中。PrintCommandPrompt
:创建一个prompt
数组,调用MakeCommandLine
函数生成命令行提示符,然后使用printf
函数输出到控制台。最后使用fflush(stdout)
强制刷新输出缓冲区,确保提示符立即显示。
获取用户输入的命令
bool GetCommandLine(char *out, int size)
{char *c = fgets(out, size, stdin);if(c == NULL) return false;out[strlen(out) - 1] = 0; // 清理\nif(strlen(out) == 0) return false;return true;
}
- 使用
fgets
函数从标准输入(键盘)读取一行命令,存储到out
数组中。 - 如果
fgets
返回NULL
,表示读取失败,返回false
。 out[strlen(out) - 1] = 0;
:fgets
会将换行符\n
也读取到字符串中,这里将其替换为字符串结束符\0
,去除换行符。- 如果读取的字符串长度为 0,说明用户没有输入有效内容,返回
false
。 - 否则返回
true
,表示成功获取到命令。
解析命令行
bool CommandParse(char *commandline)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(commandline, SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--;return g_argc > 0? true:false;
}
SEP
是一个宏定义,表示命令行参数的分隔符,这里使用空格。g_argc = 0;
:将命令行参数的实际数量初始化为 0。g_argv[g_argc++] = strtok(commandline, SEP);
:使用strtok
函数将commandline
字符串按照分隔符SEP
进行分割,获取第一个参数,并存储到g_argv
数组中。同时增加g_argc
的值。while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
:使用strtok
函数继续分割剩余的字符串,直到没有更多的参数为止。每次分割得到的参数都存储到g_argv
数组中,并增加g_argc
的值。g_argc--;
:由于最后一次分割会得到一个NULL
指针,所以需要将g_argc
的值减 1。- 如果
g_argc
大于 0,表示解析到了有效的命令行参数,返回true
;否则返回false
。
6. 检测并执行内置命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export"){if (g_argc == 2){std::string var = g_argv[1];char *equal_pos = strchr(var.c_str(), '=');if (equal_pos){*equal_pos = '\0';setenv(var.c_str(), equal_pos + 1, 1);}}return true;}else if(cmd == "alias"){if (g_argc == 3){std::string nickname = g_argv[1];std::string real_cmd = g_argv[2];alias_list[nickname] = real_cmd;}return true;}return false;
}
std::string cmd = g_argv[0];
:获取命令行的第一个参数,即命令名。cmd == "cd"
:如果命令是cd
,调用Cd
函数执行cd
命令,并返回true
。cmd == "echo"
:如果命令是echo
,调用Echo
函数执行echo
命令,并返回true
。cmd == "export"
:如果命令是export
,并且参数数量为 2,使用strchr
函数查找参数中=
的位置。如果找到,将=
替换为字符串结束符\0
,然后使用setenv
函数设置环境变量。最后返回true
。cmd == "alias"
:如果命令是alias
,并且参数数量为 3,将第一个参数作为别名,第二个参数作为实际命令,存储到alias_list
中。最后返回true
。- 如果以上条件都不满足,返回
false
,表示该命令不是内置命令。
7. 执行外部命令
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;
}
pid_t id = fork();
:使用fork
函数创建一个新的进程。fork
函数会返回两次,在父进程中返回子进程的进程 ID,在子进程中返回 0。- 子进程:如果
id == 0
,表示当前是子进程。使用execvp
函数执行命令行指定的外部命令。execvp
函数会用新的程序替换当前进程的映像,如果执行失败,会返回 -1。为了避免子进程继续执行后面的代码,使用exit(1)
终止子进程。 - 父进程:在父进程中,使用
waitpid
函数等待子进程结束。waitpid
函数会阻塞父进程,直到指定的子进程结束。status
参数用于存储子进程的退出状态。 lastcode = WEXITSTATUS(status);
:使用WEXITSTATUS
宏从status
中提取子进程的退出码,并将其存储到lastcode
中。- 最后返回 0,表示执行成功。
8. 主函数
int main()
{// 初始化环境变量InitEnv();while(true){// 输出命令行提示符PrintCommandPrompt();// 获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 解析命令行if(!CommandParse(commandline))continue;// 检测并执行内置命令if(CheckAndExecBuiltin())continue;// 执行外部命令Execute();}// 释放内存for (int i = 0; i < g_envs; ++i){free(g_env[i]);}return 0;
}
- 初始化环境变量:调用
InitEnv
函数初始化环境变量。 - 无限循环:使用
while(true)
进入一个无限循环,不断等待用户输入命令。 - 输出命令行提示符:调用
PrintCommandPrompt
函数输出命令行提示符。 - 获取用户输入的命令:创建一个
commandline
数组,调用GetCommandLine
函数获取用户输入的命令。如果获取失败,跳过本次循环,继续等待下一次输入。 - 解析命令行:调用
CommandParse
函数解析命令行。如果解析失败,跳过本次循环,继续等待下一次输入。 - 检测并执行内置命令:调用
CheckAndExecBuiltin
函数检测命令是否为内置命令。如果是,执行相应的内置命令,并跳过本次循环,继续等待下一次输入。 - 执行外部命令:如果不是内置命令,调用
Execute
函数执行外部命令。 - 释放内存:在程序结束前,使用
free
函数释放g_env
数组中分配的内存,避免内存泄漏。 - 最后返回 0,表示程序正常结束。