程序功能:模拟实现一个自己的shell执行命令行。
涉及知识:字符串输入及操作函数、子进程创建、进程等待、进程替换、环境变量及获取、添加环境变量的函数
额外需要了解的功能函数:chdir(char* path)函数——改变当前工作路径
名词解释:内建命令(Built-in Commands)是指在命令行解释器(如 Bash、Zsh 等)中直接实现的命令。这些命令不需要调用外部程序或二进制文件,而是由解释器本身提供和处理。
注意事项:
易错点 :int putenv(char *str);
putenv函数参数的指针str指向的数组,在getenv使用通过该函数导入的环境变量时,必须保证在调用getenv函数的区域中,str指针指向的数组依然有效才可以。
指针有效性问题:putenv 函数将传入的字符串指针直接接管为环境变量的一部分,而不会复制传入的字符串。因此,一旦 putenv 被调用后,传入的指针必须保持有效,直到程序退出或者重新用新值调用 putenv。
// 程序功能:模拟实现一个自己的shell执行命令行。
// 涉及知识:字符串输入及操作函数、子进程创建、进程等待、进程替换、环境变量及获取、添加环境变量的函数
// 额外需要了解的功能函数:chdir(char* path)函数——改变当前工作路径
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdio>
#include <errno.h>
#define SIZE 512 // 定义缓冲区大小默认512字节
#define NUM 32
#define CUT_CHAR " "
char cwd[SIZE*2] = {'\0'};
// 程序设计思路:
// 对象设计:shell
// 成员变量:1、string——获取命令行参数 2、string——储存命令行信息
class shell
{
public:
// 默认构造函数
// 1、获取命令行信息,包括:使用者USER,主机名HOSTNAME,当前工作路径PWD
void get_command_line()
{
// 确保环境变量都存在于默认环境变量表中,如果不存在,getenv函数会返回为NULL,这个时候需要提前使用指针接收并判断是否为NULL;
// 由于Ubuntu系统中不存在HOSTNAME默认环境变量,我们先对其使用putenv函数导入自定义的HOSTNAME环境变量
char tmp_hostname[SIZE];
snprintf(tmp_hostname, SIZE, "HOSTNAME=hecs-135712");
if (putenv(tmp_hostname) != 0)
{
perror("export env fail!!!");
exit(errno);
}
user = getenv("USER");
hostname = getenv("HOSTNAME");
pwd = getenv("PWD");
if (user == nullptr)
{
std::cerr << "Error: user variables not set." << std::endl;
exit(errno);
}
if (hostname == nullptr)
{
std::cerr << "Error: hostname variables not set." << std::endl;
exit(errno);
}
if (pwd == nullptr)
{
std::cerr << "Error: pwd variables not set." << std::endl;
exit(errno);
}
char *tmp = getenv("HOME");
if (strncmp(pwd, tmp, strlen(tmp)) == 0)
{
pwd[0] = '~';
strcpy(pwd + 1, pwd + strlen(tmp));
}
}
void print_command_line()
{
printf("%s@%s:%s> ", user, hostname, pwd);
fflush(stdout);
}
void get_command_option()
{
char buffer[SIZE] = {'\0'};
fgets(buffer, sizeof(buffer), stdin);
buffer[strlen(buffer) - 1] = '\0';
argv[0] = strtok(buffer, CUT_CHAR);
int index = 1;
while (argv[index++] = strtok(NULL, CUT_CHAR));
}
void execute_command()
{
// 创建子进程,使用进程替换执行对应的命令
int id = fork();
if (id == 0)
{
int ret = execvp(argv[0], argv);
if (ret == -1)
{
perror("process replace fail!!!");
exit(errno);
}
}
else if (id > 0)
{
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (WIFEXITED(status))
{
int lastnode = WEXITSTATUS(status);
if (lastnode != 0)
{
printf("%s:%s\n", argv[0], strerror(errno));
}
}
}
else
{
perror("build child process fail!!!");
exit(errno);
}
}
void test(){
printf("test:");
puts(getenv("PWD"));
}
void cd()
{
const char *path = argv[1];
if (argv[1] == NULL)
{
path = getenv("HOME");
}
int ret = chdir(path);
if (ret == -1)
{
perror("change directory fail!!!");
exit(errno);
}
char str[SIZE] = {'\0'};
getcwd(str, sizeof(str));
//char cwd[SIZE*2] = {'\0'}; 定义为全局变量
//原因:你在执行cd命令后更新了PWD环境变量,但是在下一个循环迭代中,getenv("PWD")返回空指针。
//这是因为putenv函数将传入的字符串指针接管为环境变量的一部分,并不会复制传入的字符串,而是直接使用传入的指针。
//这意味着,一旦你调用putenv(cwd)之后,cwd指向的内存必须保持有效,直到程序结束或者通过putenv重新设置新的值。
snprintf(cwd, sizeof(cwd), "PWD=%s", str);
puts(cwd);
putenv(cwd);
}
bool is_bash_command()
{
bool yes = false;
if (strcmp(argv[0], "cd") == 0)
{
cd();
yes = true;
}
return yes;
}
private:
char *user = NULL;
char *hostname = NULL;
char *pwd = NULL;
char *argv[NUM] = {nullptr}; // 存储命令及选项
};
int main()
{
shell tmp;
while (1)
{
tmp.test();
//获取命令行
tmp.get_command_line();
// 打印命令行
tmp.print_command_line();
// 获取命令及选项
tmp.get_command_option();
// 判断是否为内建命令
if (!tmp.is_bash_command())
// 执行命令q
tmp.execute_command();
}
return 0;
}