当前位置: 首页 > news >正文

简单的 shell 程序

整体思路

一个简单的 shell 程序的工作流程如下:

  1. 初始化环境:在启动时从系统获取环境变量。
  2. 循环等待用户输入:不断输出命令行提示符,等待用户输入命令。
  3. 解析命令:把用户输入的命令解析成可执行的格式。
  4. 执行命令:判断是内置命令还是外部命令,然后执行相应操作。

 

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,表示程序正常结束。
http://www.xdnf.cn/news/185203.html

相关文章:

  • 德州仪器(TI)—TDA4VM芯片详解—目录
  • 十七、系统可靠性分析与设计
  • Vue3 + OpenLayers 开发教程 (六)WebGL渲染优化
  • 【Nova UI】十二、打造组件库之按钮组件(上):迈向功能构建的关键一步
  • Linux系统类型及常用操作命令总结
  • Linux一个系统程序——进度条
  • QT中的事件及其属性
  • 大学之大:伦敦政治经济学院2025.4.27
  • onnexruntime u2net sharp 实现开源图片处理软件
  • vue 打包设置
  • DFPatternFunctor遍历计算图
  • 【博客系统】博客系统第一弹:博客系统项目配置、MyBatis-Plus 实现 Mapper 接口、处理项目公共模块:统一返回结果、统一异常处理
  • 关于华为高斯数据库出现Invalid or unsupported by client SCRAM mechanisms定位解决的过程
  • -信息革命-
  • OpenManus云端部署及经典案例应用
  • 心磁图技术突破传统局限!心血管疾病早筛迈入“三零“新时代
  • TV launcher官方下载-tv launcher汉化版-tv桌面启动器极简下载
  • c++17 对于临时对象作为右值的优化
  • MRI学习笔记-conjunction analysis
  • Linux——线程(2)线程互斥(锁)
  • 机器学习 | 基于回归模型的交通需求预测案例分析及代码示例
  • 日本IT|UIUX主要的工作都是哪些?及职业前景
  • 【每日随笔】文化属性 ② ( 高维度信息处理 | 强者思维形成 | 认知重构 | 资源捕获 | 进化路径 )
  • LangChain构建大模型应用之RAG
  • 使用ROS实现多机通讯
  • 线上查询车辆出险记录:快速掌握事故情况!
  • 大模型API密钥的环境变量配置(大模型API KEY管理)(将密钥存储在环境变量)(python-dotenv)(密钥管理)
  • 数据结构(七)---链式栈
  • AI看论文自动生成代码库:Paper2Code如何革新科研复现?
  • 函数式链表:Python编程的非常规 “链” 接