承接上文Linux 进程的创建、终止、等待与程序替换保姆级讲解-CSDN博客,涉及所用到的代码,本文所绑定的资源就是上篇文章的主要代码。
完整代码在文章末尾
目录
c.完整代码(改前)printf进行打印的时候数据存在缓冲区:
[a."ls -a -l -i"本质上是一个字符串,使用 fgets() 获取一整个字符串](#a.“ls -a -l -i”本质上是一个字符串,使用 fgets() 获取一整个字符串)
[b.注意当在echo :%d后加换行符:](#b.注意当在echo :%d后加换行符:)
[shell 1.0 代码,程序只能运行一次](#shell 1.0 代码,程序只能运行一次)
[n + 1:将命令多次执行](#n + 1:将命令多次执行)
[shell 2.0 需补坑完整代码:](#shell 2.0 需补坑完整代码:)
[6.内建命令echo ?问题](#6.内建命令echo ?问题)
[7.自定义环境变量export HELLO=12345](#7.自定义环境变量export HELLO=12345)
a.使用strdup()函数复制gArgv[1],arg指向字符串首地址,避免修改原始命令字符串
[b. 使用strchr()函数查询是否有 = ,如果有则返回 = 的地址,没有则返回NULL](#b. 使用strchr()函数查询是否有 = ,如果有则返回 = 的地址,没有则返回NULL)
c.判断是否为NULL,如果为空说明export使用的格式错误
[d.再将*eq指向的 = ,位置置为\0,截取arg = 前的字符串,eq+1,得到 = 后面的字符串。](#d.再将*eq指向的 = ,位置置为\0,截取arg = 前的字符串,eq+1,得到 = 后面的字符串。)
[2. echo HELLO 的时候需要将HELLO替换成它对应的值12345,从而输出12345](#2. echo HELLO 的时候需要将HELLO替换成它对应的值12345,从而输出12345)
[a.添加变量替换函数 ReplaceEnvVars():](#a.添加变量替换函数 ReplaceEnvVars():)
1.上文所写到的程序可以执行系统的所有命令,包括自己写的可执行程序。
- 在执行命令的时候,只执行了一次就结束,本篇文章主要讲如何让程序不断地执行不同的命令(可执行程序) -----> shell ---> 模拟实现命令行
实际上我们所看到的简单的命令行,本质上是一个字符串,并且我们输入的命令也是字符串 。将读进来的字符串进行分析,解析成命令,再fork(), 再exec, 这条命令就执行了。
bash
pupu@VM-8-15-ubuntu:~/bitclass/class_20/myshell$
bash 本质上是一个进程,有独立的pid
显示进程列表的表头,以及列出
bash
进程信息,并且过滤掉grep bash自身进程:
bashps ajx | head -1 && ps ajx | grep bash | grep -v grep
得到:
bashPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1366539 1366540 1366540 1366540 pts/0 1368748 Ss 1002 0:00 -bash 1372289 1372290 1372290 1372290 pts/1 1386639 Ss 1002 0:00 -bash
以上算本文的周边笔记知识提及。
1.实现编写代码输出一个命令行
a.如何获取自己的用户名,主机名,路径名?
环境变量可以通过函数getenv() 头文件<stdlib.h>来获取,获取自己的用户名,主机名,路径名从环境变量**(命令行输入env)**里定向获取。
查取到用户名为
LOGNAME=pupu

测试获取登录名:
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#define SIZE 512
const char *getusername()
{
const char *name = getenv("LOGNAME");
if(name == NULL) return "none";
return name;
}
int main()
{
//1.我们需要自己输出一个命令行
char output[SIZE];
printf("name: %s\n",getusername());
return 0;
}
输出结果:获取成功

我只想要当前路径,往往PWD中所存储的是绝对路径,如何截取字符串获得当前路径(可以定义尾指针,到 ' / '截取停止,请看目录5.将命令行路径改为相对路径)
cpp
PWD=/home/pupu/bitclass/class_20/myshell
ubuntu系统环境变量中默认没有HOSTNAME,centos系统环境变量中可以直接通过env查取到,可以用类似于获取用户名的方式来做:
b.ubuntu的HOSTNAME的获取方法:
cppconst char *GetHostName() { char buffer[256]; char *hostname = buffer; if (gethostname(hostname, sizeof(buffer)) == 0) return hostname; return "none"; }
此时,我的代码存在 内存作用域问题 :
buffer
是局部数组 ,在函数返回后其内存会被释放,导致返回的hostname
指针成为 悬垂指针,访问时可能输出随机内容或截断的字符串。解决办法:
将 buffer 声明为 static(处于静态存储区) , 延长其生命周期至程序结束:
cppconst char *GetHostName() { static char buffer[512]; // 静态存储期,函数返回后内存仍有效 if (gethostname(buffer, sizeof(buffer)) == 0) { buffer[sizeof(buffer)-1] = '\0'; // 确保字符串终止 return buffer; } return "none"; }
此时便能获取到正确的hostname了。
c.完整代码(改前)printf进行打印的时候数据存在缓冲区:
cpp#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<stdlib.h> #define SIZE 512 const char *GetUserName() { const char *name = getenv("LOGNAME"); if(name == NULL) return "none"; return name; } const char *GetHostName() { static char buffer[256]; char *hostname = buffer; if (gethostname(hostname, sizeof(buffer)) == 0) return hostname; return "none"; } //获取当前的路径 const char *GetCwd() { const char *cwd = getenv("PWD"); if(cwd == NULL) return "none"; return cwd; } void MakeCommendLine(char line[], size_t size) { //获取三个字符串 const char *username = GetUserName(); const char *hostname = GetHostName(); const char *cwd = GetCwd(); //拼接字符串 snprintf(line, size, "%s@%s:%s^_^ -> ", username, hostname, cwd); } int main() { //1.我们需要自己输出一个命令行 char commendline[SIZE]; MakeCommendLine(commendline, sizeof(commendline)); printf("%s", commendline); sleep(5); return 0; }
但是在运行程序的时候会发现,我们想要的字符串会等上5s才打印出来,这是因为printf在进行打印的时候数据是会写在缓冲区中的,当程序结束时才会出来,这里的想法是将制作命令行与打印命令行放进一个函数里,并使用fflush(stdout),刷新标准输出流
stdout
,
将缓冲区中的输出数据立即写到输出设备:此时运行代码:就会直接先打印出我们制作的命令行。延迟5s的原因是为了能够看到这个效果:
2.实现编写代码获取用户命令字符串
a."ls -a -l -i"本质上是一个字符串,使用 fgets() 获取一整个字符串
cppchar *fgets(char *s, int size, FILE *stream);
按行从特定的文件流当中获取指定的内容,成功获取字符串时,返回的是获取到的字符串的起始地址,失败则返回none。
如图:为了使代码更具有可读性
运行此代码进行测试:
cpp#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<stdlib.h> #define SIZE 512 const char *GetUserName() { const char *name = getenv("LOGNAME"); if(name == NULL) return "none"; return name; } const char *GetHostName() { static char buffer[256]; char *hostname = buffer; if (gethostname(hostname, sizeof(buffer)) == 0) return hostname; return "none"; } //获取当前的路径 const char *GetCwd() { const char *cwd = getenv("PWD"); if(cwd == NULL) return "none";//暂时这样写,后续会修改 return cwd; } void MakeCommendLineAndPrint() { //实现输出一个命令行 char line[SIZE]; //获取三个字符串 const char *username = GetUserName(); const char *hostname = GetHostName(); const char *cwd = GetCwd(); //拼接字符串 snprintf(line, sizeof(line), "%s@%s:%s^_^ -> ", username, hostname, cwd); printf("%s", line); fflush(stdout); } int main() { //自己输出一个命令行 MakeCommendLineAndPrint(); //获取用户命令字符串 //1.再定一个缓冲区 char usercommend[SIZE]; //2.获取:从标准输入流中获取 char *s = fgets(usercommend, sizeof usercommend, stdin);//起到一个输入停留的作用 if(s == NULL) return 1; printf("echo : %s", usercommend); return 0; }
输出结果:
b.注意当在echo :%d后加换行符:
原代码并没有加\n,当加上\n后按理来说只会多一个空行,这里却空了两行相当于有两个\n?是为什么:因为,在我输完ls -a -l之后还摁了回车,回车符也被读入\r\n。
修改:
运行代码:此时就正常打印出
为了使代码具有可读性,我封装获取命令字符串的代码:
3.分割获取的用户命令字符串
a.封装一个函数SplitCommend()用于分割命令行字符串,创建一个全局变量的表gArgv[NUM],#define NUM 32,分隔符:define SEP " "
需要做到的是:将"ls -a -l -n" ----> "ls", "-a", "-l", "-n"
使用strtok函数,将一个子串,按照指定的分隔符进行分割,返回值就是从左往右分割出的第一个字符,第一次调用时把字符串保存下来,将这个位置设置为NULL,第二次调用就会对历史字符串继续分割,最后为NULL的时候,就结束了。
cppchar *strtok(char *str, const char *delim);
define SEP**" "** 请注意,分隔符得设置成字符串才能传进去,不能是' ' 字符。
请阅读下面我修改后代码,对代码的提示:
运行代码:此时已将字符分割存入表内
n.创建子进程执行命令
只能让子进程去执行具体原因参见我的上一篇博客进程替换部分:Linux 进程的创建、终止、等待与程序替换函数 保姆级讲解-CSDN博客
图中就是我对代码进行的修改
运行结果:已经成功,再删除多余代码就行。
将函数封装,删去多余代码
运行结果:
shell 1.0 代码,程序只能运行一次
cpp#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<stdlib.h> #include<string.h> #include<errno.h> #define SIZE 512 #define ZERO '\0' #define SEP " " #define NUM 32 void Die() { exit(1); } const char *GetUserName() { const char *name = getenv("LOGNAME"); if(name == NULL) return "none"; return name; } const char *GetHostName() { static char buffer[256]; char *hostname = buffer; if (gethostname(hostname, sizeof(buffer)) == 0) return hostname; return "none"; } //获取当前的路径 const char *GetCwd() { const char *cwd = getenv("PWD"); if(cwd == NULL) return "none"; return cwd; } void MakeCommendLineAndPrint() { //实现输出一个命令行 char line[SIZE]; //获取三个字符串 const char *username = GetUserName(); const char *hostname = GetHostName(); const char *cwd = GetCwd(); //拼接字符串 snprintf(line, sizeof(line), "%s@%s:%s^_^ -> ", username, hostname, cwd); printf("%s", line); fflush(stdout); } //获取用户命令字符串 int GetUserCommend(char commend[], size_t n) { //2.再定一个缓冲区 //2.1.获取:从标准输入流中获取 char *s = fgets(commend, n, stdin); if(s == NULL) return -1; commend[strlen(commend) - 1] = ZERO; return strlen(commend); } //定义一张全局的表 char *gArgv[NUM]; void SplitCommend(char commend[], size_t n) { //"ls -a -l -n" ---> "ls", "-a", "-l", "-n" gArgv[0] = strtok(commend, SEP); int index = 1; while(gArgv[index++] = strtok(NULL, SEP));//故意写成 = ,表示先赋值,再判断。刚好让gArgv最后一个元素是NULL,并且while判断结束 } void ExecuteCommend() { //n.执行命令: pid_t id = fork(); //创建的子进程失败 if(id < 0) Die(); else if(id == 0) { //child execvp(gArgv[0],gArgv); exit(errno); } else { int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid == id) { // printf("father wait success!, child exit code : %d\n", WEXITSTATUS(status)); } } } int main() { //1.自己输出一个命令行 MakeCommendLineAndPrint(); //2.获取用户命令字符串 char usercommend[SIZE]; int n = GetUserCommend(usercommend, sizeof(usercommend)); if(n <= 0) return 1; //printf("echo : %s\n", usercommend); //3.命令行字符串分割 SplitCommend(usercommend, sizeof(usercommend)); for(int i = 0; gArgv[i]; i++)//当i = 最后一个字符串的下标时,因为为NULL,所以退出循环 { // printf("gArgv[%d]: %s\n", i, gArgv[i]); } //执行命令 ExecuteCommend(); return 0; }
以上我所写的shell只能跑一次,想要像真正的命令行一样就需要可以执行多次。
n + 1:将命令多次执行
while 循环,不退出就能一直执行:
运行结果:
以上就是一个简单shell的制作。
shell 2.0 需补坑完整代码:
cpp#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<stdlib.h> #include<string.h> #include<errno.h> #define SIZE 512 #define ZERO '\0' #define SEP " " #define NUM 32 void Die() { exit(1); } const char *GetUserName() { const char *name = getenv("LOGNAME"); if(name == NULL) return "none"; return name; } const char *GetHostName() { static char buffer[256]; char *hostname = buffer; if (gethostname(hostname, sizeof(buffer)) == 0) return hostname; return "none"; } //获取当前的路径 const char *GetCwd() { const char *cwd = getenv("PWD"); if(cwd == NULL) return "none"; return cwd; } void MakeCommendLineAndPrint() { //实现输出一个命令行 char line[SIZE]; //获取三个字符串 const char *username = GetUserName(); const char *hostname = GetHostName(); const char *cwd = GetCwd(); //拼接字符串 snprintf(line, sizeof(line), "%s@%s:%s^_^ -> ", username, hostname, cwd); printf("%s", line); fflush(stdout); } //获取用户命令字符串 int GetUserCommend(char commend[], size_t n) { //2.再定一个缓冲区 //2.1.获取:从标准输入流中获取 char *s = fgets(commend, n, stdin); if(s == NULL) return -1; commend[strlen(commend) - 1] = ZERO; return strlen(commend); } //定义一张全局的表 char *gArgv[NUM]; void SplitCommend(char commend[], size_t n) { //"ls -a -l -n" ---> "ls", "-a", "-l", "-n" gArgv[0] = strtok(commend, SEP); int index = 1; while(gArgv[index++] = strtok(NULL, SEP));//故意写成 = ,表示先赋值,再判断。刚好让gArgv最后一个元素是NULL,并且while判断结束 } void ExecuteCommend() { //n.执行命令: pid_t id = fork(); //创建的子进程失败 if(id < 0) Die(); else if(id == 0) { //child execvp(gArgv[0],gArgv); exit(errno); } else { int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid == id) { // printf("father wait success!, child exit code : %d\n", WEXITSTATUS(status)); } } } int main() { int quit = 0; while(!quit) { //1.自己输出一个命令行 MakeCommendLineAndPrint(); //2.获取用户命令字符串 char usercommend[SIZE]; int n = GetUserCommend(usercommend, sizeof(usercommend)); if(n <= 0) return 1; //printf("echo : %s\n", usercommend); //3.命令行字符串分割 SplitCommend(usercommend, sizeof(usercommend)); for(int i = 0; gArgv[i]; i++)//当i = 最后一个字符串的下标时,因为为NULL,所以退出循环 { // printf("gArgv[%d]: %s\n", i, gArgv[i]); } //执行命令 ExecuteCommend(); } return 0; }
nn:填补上述shell代码的坑(cd无用的问题)
我们当前的shell无法进行路径的切换:
每个进程都会记录当前所属的路径,所以父进程有,子进程有。
原因:
我的shell中子进程进行cd ..,和父进程没有关系,也就是和bash没有关系,因此不会切换。
因此当需执行的命令是 cd 时,应该让此命令给父进程执行 -----> cd是内建命令
4.检查命令是否是内建命令(只有bash能执行的命令)
chdir()更改当前的工作路径
这里需要使用到chdir()系统调用命令,<unistd.h>, 用于更改当前的工作路径
cppint chdir(const char *path);
更改我当前的工作路径:
cd 命令一般只有 cd(进入家目录), cd 相对路径/绝对路径(进入路径所处目录),cd ..(进入上级目录),cd ~(进入家目录),cd -(打印上级目录并进入上级目录,在这里没有写)
因此gArgv[1],便是cd的选择命令,如果为空,则进入家目录,不为空就可以直接 使用,直接调用系统命令,更改当前的工作路径。更改成功,但是依然存在问题:命令行中的路径始终未发生改变
因此我们再次更改代码,将输入的有效路径传给cwd,并更新环境变量:
运行结果:
这是因为,此时我将获取到的字符串直接给cwd了,并且还更新了环境变量导致PWD="path",当我输入 ..,那么PWD=..,因此我们需要得到当前工作目录的绝对路径,再将他的值传给cwd,更新环境变量。
**getcwd()**获取进程当前工作目录的绝对路径
这告诉我们,每次刷新命令行路径的时候也需要采用绝对路径 ,使用系统调用命令getcwd()
cppchar *getcwd(char *buf, size_t size);
这里使用temp[SIZE*2]用于存储获得的绝对路径
运行结果:因为我定义的cwd[SIZE*2] -->1024个字节,PWD+%s --->1028个字节,超出范围。
因此我直接:
运行结果:完全正确
5.将命令行路径通过使用绝对路径改为相对路径
在centos系统之下,命令行路径只会记录当前的相对路径:

因此就需要我们对路径进行剪切:
定义一个宏函数(解释:看目录)
运行结果:
此时还不够完美,其中还有 ' / ',这是因为指针指向/停止,将 / 的地址传回来,因此直接对cwd + 1就可以:
运行结果:
当到达根目录时,却没有路径字符串了
再修改:
运行结果:
为什么定义宏,以及使用do{}while(0)?
cpp#define SkipPath(p) do{ p += (strlen(p)- 1); while(*p != '/') p--; }while(0)
首先,这里涉及到对指针做操作 ,如果我想封装一个函数 对这个指针操作,那就需要传二级指针, 因此我们用宏,使cwd -被替换成--> p ,do{}while(0)形成代码块 ,并且do{}while()后面可以随便带' ;',方便后续的使用:就很像一个函数了,特别是需要写在 if 里面,也不会出什么错。
当在写宏函数需要用代码块的的时候建议写在do{}while()里面(编码小技巧)
echo $?,返回最后一次进程的返回值(退出码 ):

运行结果:

6.内建命令echo $?问题
图片里为什么还要把lastcode --> 0 不懂可以看:Linux 进程的创建、终止、等待与程序替换函数 保姆级讲解-CSDN博客
运行结果:
7.自定义环境变量export HELLO=12345
当我们导入环境变量的时候:
- export HELLO=12345,又需要识别到是内建命令,通过strcmp来判断。
创建函数Export()来执行此代码:
Export()函数
a.
使用strdup()函数复制gArgv[1],arg指向字符串首地址
,避免修改原始命令字符串
cppchar *arg = strdup(gArgv[1]);
b. 使用strchr()函数查询是否有 = ,如果有则返回 = 的地址,没有则返回NULL
cppchar *eq = strchr(arg, '=');
c.判断是否为NULL,如果为空说明export使用的格式错误
cppif (eq == NULL) { fprintf(stderr, "export: invalid format\n"); free(arg); // 错误分支也要释放内存 return; }
d.再将*eq指向的 = ,位置置为\0,截取arg = 前的字符串,eq+1,得到 = 后面的字符串。
cpp*eq = '\0';
e.**setenv()**设置环境变量:
cpp#include <stdlib.h> // 需要包含头文件 int setenv(const char *name, const char *value, int overwrite);
参数:
name
:环境变量名(如"PATH"
)|value
:要设置的值(如"/usr/bin"
)。
overwrite
:1
(非零) :若变量已存在,则覆盖旧值。0
:若变量已存在,则保留旧值,不修改。返回值 :
0
:成功。-1
:失败(错误原因存于errno
,如ENOMEM
内存不足)。将(arg = HELLO,eq = 12345,1-->确认覆盖),将环境变量名为HELLO的值确认使用12345覆盖。如果原本这个环境变量不存在,则在env中添加这个新的环境变量。并且判断是否创建,执行成功。
cppif (setenv(arg, eq+1, 1) != 0) { perror("export"); }
最后释放arg所指向的空间
cppfree(arg); // 正常路径释放内存
2. echo HELLO 的时候需要将HELLO替换成它对应的值12345,从而输出12345
执行
echo $HHH
时,Shell 本应进行以下操作:
变量替换 :将
$HHH
替换为环境变量HHH
的值。执行命令 :调用
echo
并传入替换后的参数。a.添加变量替换函数 ReplaceEnvVars():
如果$后跟的是?就直接使用前面写的获取退出码的那个代码,这里要排除一下
cppvoid ReplaceEnvVars() { for (int i = 0; gArgv[i] != NULL; i++) { if(gArgv[i][0] == '$') { if(gArgv[i][1] != '?')//?就直接使用前面写的获取退出码的那个代码,这里要排除一下 { char *var_name = gArgv[i] + 1; //跳过'$',获取变量名 char *value = getenv(var_name); if(value) { //如果这个变量名已经在环境变量中存在 gArgv[i] = strdup(value); } else { gArgv[i] = strdup(""); } } } } }
将这个函数在调用判断内建命令的函数前进行调用:
运行代码:
以上就是shell的模拟实现。
完整代码
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += (strlen(p)- 1); while(*p != '/') p--; }while(0)
char cwd[SIZE*4];
//定义一张全局的表
char *gArgv[NUM];
int lastcode = 0;
void Die()
{
exit(1);
}
const char *GetHome()
{
const char *home = getenv("HOME");
if(home == NULL) return "/";
return home;
}
const char *GetUserName()
{
const char *name = getenv("LOGNAME");
if(name == NULL) return "none";
return name;
}
const char *GetHostName()
{
static char buffer[256];
char *hostname = buffer;
if (gethostname(hostname, sizeof(buffer)) == 0) return hostname;
return "none";
}
//获取当前的路径
const char *GetCwd()
{
const char *cwd = getenv("PWD");
if(cwd == NULL) return "none";
return cwd;
}
void MakeCommendLineAndPrint()
{
//实现输出一个命令行
char line[SIZE];
//获取三个字符串
const char *username = GetUserName();
const char *hostname = GetHostName();
const char *cwd = GetCwd();
SkipPath(cwd);
//拼接字符串
snprintf(line, sizeof(line), "%s@%s:%s^_^ -> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
printf("%s", line);
fflush(stdout);
}
//获取用户命令字符串
int GetUserCommend(char commend[], size_t n)
{
//2.再定一个缓冲区
//2.1.获取:从标准输入流中获取
char *s = fgets(commend, n, stdin);
if(s == NULL) return -1;
commend[strlen(commend) - 1] = ZERO;
return strlen(commend);
}
void SplitCommend(char commend[], size_t n)
{
//"ls -a -l -n" ---> "ls", "-a", "-l", "-n"
gArgv[0] = strtok(commend, SEP);
int index = 1;
while(gArgv[index++] = strtok(NULL, SEP));//故意写成 = ,表示先赋值,再判断。刚好让gArgv最后一个元素是NULL,并且while判断结束
}
void ExecuteCommend()
{
//n.执行命令:
pid_t id = fork();
//创建的子进程失败
if(id < 0) Die();
else if(id == 0)
{
//child
execvp(gArgv[0],gArgv);
exit(errno);
}
else
{
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
// printf("father wait success!, child exit code : %d\n", WEXITSTATUS(status));
lastcode = WEXITSTATUS(status);
if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
}
}
}
//判断是哪一种cd
void Cd()
{
const char* path = gArgv[1];
if(path == NULL) path = GetHome();
//PATH 一定存在
chdir(path);
//刷新环境变量
char temp[SIZE*2];
getcwd(temp, sizeof(temp));
snprintf(cwd, sizeof(cwd),"PWD=%s", temp);//将cwd设置为一个全局变量,实时更新
putenv(cwd); //再更新环境变量
}
void ReplaceEnvVars()
{
for (int i = 0; gArgv[i] != NULL; i++)
{
if(gArgv[i][0] == '$')
{
if(gArgv[i][1] != '?')
{
char *var_name = gArgv[i] + 1; //跳过'$',获取变量名
char *value = getenv(var_name);
if(value)
{
//如果这个变量名已经在环境变量中存在
gArgv[i] = strdup(value);
}
else
{
gArgv[i] = strdup("");
}
}
}
}
}
void Export()
{
if(!gArgv[1])
{
fprintf(stderr, "export: missing argument!\n");
return;
}
char *arg = strdup(gArgv[1]);
char *eq = strchr(arg,'=');//查找是否有 = ,如果有就返回 = 的地址
if(eq == NULL)
{
fprintf(stderr, "export: invalid format\n");
free(arg);
return;
}
*eq = '\0';//将 = 的位置的字符置为\0提前结束
if(setenv(arg, eq+1, 1) != 0)
{
perror("export");
}
free(arg);
}
//检查是否是内建命令
int CheckBuildin()
{
int yesorno = 0;
const char *enter_cmd = gArgv[0];
if(strcmp(enter_cmd, "cd") == 0)
{
yesorno = 1;
Cd();
}
else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1],"$?") == 0)
{
yesorno = 1;
printf("%d\n", lastcode);
lastcode = 0;
}
else if(strcmp(enter_cmd, "export") == 0)
{
Export();
yesorno = 1;
}
return yesorno;
}
int main()
{
int quit = 0;
while(!quit)
{
//1.自己输出一个命令行
MakeCommendLineAndPrint();
//2.获取用户命令字符串
char usercommend[SIZE];
int n = GetUserCommend(usercommend, sizeof(usercommend));
if(n <= 0) return 1;
//printf("echo : %s\n", usercommend);
//3.命令行字符串分割
SplitCommend(usercommend, sizeof(usercommend));
for(int i = 0; gArgv[i]; i++)//当i = 最后一个字符串的下标时,因为为NULL,所以退出循环
{
// printf("gArgv[%d]: %s\n", i, gArgv[i]);
}
ReplaceEnvVars();
//4,检查是否是内建命令
n = CheckBuildin();
if(n) continue;
//执行命令
ExecuteCommend();
}
return 0;
}
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。