企业介绍微网站怎么做的,六数字域名做网站好不好,wordpress 显示小工具,江门网站建设外包目录  
实现思路 
1. 交互 获取命令行 
2. 子串分割 解析命令行 
3. 指令的判断 内建命令 
4. 普通命令的执行 补充#xff1a;vim 文本替换 
整体代码 
重点思考 
1.getenv和putenv是什么意思 
2.代码extern char **environ; 
3.内建命令是什么 
4.lastcode  WEXITSTATUS(sta… 目录  
实现思路 
1. 交互 获取命令行 
2. 子串分割 解析命令行 
3. 指令的判断 内建命令 
4. 普通命令的执行 补充vim 文本替换 
整体代码 
重点思考 
1.getenv和putenv是什么意思 
2.代码extern char **environ; 
3.内建命令是什么 
4.lastcode  WEXITSTATUS(status); 
5.execvp(_argv[0], _argv);的调用 
6._argc_argv 实现思路 
1. 交互 获取命令行 
显示提示符和获取用户输入 
Shell本质是一个死循环不断地显示提示符和获取用户输入。 
memset 函数 
memset 函数用于将一段内存区域设置为指定的值。它的原型是 
void *memset(void *s, int c, size_t n);参数说明 s指向要填充的内存区域的指针。  c要设置的值以无符号字符形式传递但实际存储在内存中的每个字节的值是该无符号字符的值。  n要设置的字节数。  
示例用法 
char command_line[NUM];
memset(command_line, \0, sizeof(command_line) * sizeof(char));这里的代码表示将 command_line 数组的每个字节都设置为 \0空字符确保初始化整个数组。 
fgets 函数 
fgets 函数用于从指定的输入流读取字符串。它的原型是 
char *fgets(char *s, int n, FILE *stream);参数说明 s指向存储读取数据的字符数组的指针。  n要读取的最大字符数包括终止字符 \0。  stream输入流通常是 stdin 用于标准输入。  
示例用法 
fgets(command_line, NUM, stdin);这行代码表示从标准输入读取最多 NUM-1 个字符预留一个字符用于终止字符 \0到 command_line 数组中。 
综合示例 
结合起来代码片段如下所示 
char command_line[NUM];
memset(command_line, \0, sizeof(command_line) * sizeof(char));
fgets(command_line, NUM, stdin);这段代码的作用是 使用 memset 函数将 command_line 数组的所有字节都设置为 \0即初始化数组。  使用 fgets 函数从标准输入读取最多 NUM-1 个字符并存储在 command_line 数组中。  这样处理后command_line 数组会包含从输入读取的字符串并且如果字符串的长度小于 NUM数组中剩余的字节会保持为 \0。 
以下是实现这两个步骤的代码 
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h#define NUM 1024char command_line[NUM]; // 用来接收命令行内容int main(void) {while (1) {/* Step1显示提示符 */printf([用户主机 当前目录] # );fflush(stdout);/* Step2获取用户输入 */memset(command_line, \0, sizeof(command_line));fgets(command_line, NUM, stdin);  // 从键盘获取输入command_line[strlen(command_line) - 1]  \0; // 消除 \nprintf(%s\n, command_line);}
}通过上述代码我们可以实现提示用户输入并获取用户输入。 
注意点 
执行发现有空行怎么办 我们利用 fgets 函数从键盘上获取标准输入 stdin获取到 C 风格的字符串 
注意默认会添加 \0 我们先把获取到的结果 command_line 打印出来看看 因为 command_line 里有一个 \n我们把它替换成 \0 即可 command_line[strlen(command_line) - 1]  \0;  // 消除 \0 2. 子串分割 解析命令行 
获取用户输入后我们需要将接收到的字符串拆分为命令及其参数。 
将接收到的字符串拆开 
通过 strtok 函数我们可以将一个字符串按照特定的分隔符打散依次返回子串 
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h#define NUM 1024
#define SEP  
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf([用户主机 当前目录] # );fflush(stdout);memset(command_line, \0, sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1]  \0;/* 将接收到的字符串拆开 */command_args[0]  strtok(command_line, SEP);int idx  1;while ((command_args[idx]  strtok(NULL, SEP)));/* 打印拆分结果 */for (int i  0; i  idx - 1; i) {printf(%d : %s\n, i, command_args[i]);}}
}通过这段代码我们可以将输入的命令行字符串拆分成多个子字符串并打印出来。 strtok 函数的原型为 
char *strtok(char *str, const char *delim);参数说明 str要进行分割的字符串第一次调用时传入要分割的字符串后续调用时传入 NULL 即可。  delim分隔符用于指定分割字符串的字符。  
示例用法 
在代码中使用了 strtok 函数将 command_line 字符串按照 SEP 分隔符进行切割并将每个子字符串存储在 command_args 数组中。 
command_args[0]  strtok(command_line, SEP);
int idx  1;这里的代码首先将 command_line 字符串按照 SEP 分隔符切割成子字符串并将第一个子字符串的指针存储在 command_args[0] 中。然后 
利用循环逐个获取剩余的子字符串并将它们存储在 command_args 数组中使用 idx 来索引。 
3. 指令的判断 内建命令 
为了实现一些特定功能如路径切换我们需要在Shell中实现内建命令。 
内建命令实现路径切换 
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h#define NUM 1024
#define SEP  
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];/* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {return chdir(new_path);
}int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf([用户主机 当前目录] # );fflush(stdout);memset(command_line, \0, sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1]  \0;/* 将接收到的字符串拆开 */command_args[0]  strtok(command_line, SEP);int idx  1;while ((command_args[idx]  strtok(NULL, SEP)));/* 判断并执行内建命令 */if (strcmp(command_args[0], cd)  0  command_args[1] ! NULL) {ChangeDir(command_args[1]);continue;}/* 执行普通命令 */}
}这段代码通过判断输入的命令是否为 cd 来执行路径切换而无需创建子进程。 
getcwd用于获取当前工作目录当前目录的路径。该函数的声明如下 char *getcwd(char *buf, size_t size);  函数参数说明 
buf指向存储当前工作目录路径的缓冲区size缓冲区的大小 
函数返回值 如果函数调用成功则返回指向存储当前工作目录路径的缓冲区的指针如果函数调用失败则返回NULL。 
通过调用getcwd函数可以获取当前程序所在的工作目录路径。 chdir用于改变当前工作目录当前目录的路径。该函数的声明如下 int chdir(const char *path);  函数参数说明 
path要设置为当前工作目录的路径 
函数返回值 如果函数调用成功则返回0如果函数调用失败则返回-1并设置errno来指示错误的类型。 
4. 普通命令的执行 
最后我们实现普通命令的执行包括创建子进程并执行用户输入的命令。 
创建进程  程序替换 
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include sys/wait.h
#include sys/types.h#define NUM 1024
#define SEP  
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void)
{while (1) {/* Step1显示提示符 */printf([lvy我的主机名 当前目录] # );fflush(stdout);/* Step2获取用户输入 */memset (command_line, \0, sizeof(command_line) * sizeof(char));fgets(command_line, NUM, stdin);  /* 从键盘获取标准输入stdin 获取到 C 风格的字符串默认添加 \0 */command_line[strlen(command_line) - 1]  \0;  // 消除 \0/* Step3: 将接收到的字符串拆开 - 字符串切分 */command_args[0]  strtok(command_line, SEP);int idx  1;/* 这里的  是故意这么写的因为 strtok 截取成功返回字符串起始地址截取失败返回 NULL */while (command_args[idx]  strtok(NULL, SEP));//我们来测试一下看看 // for (int i  0; i  idx; i) {//     printf(%d : %s\n, i, command_args[i]);// }// printf(%s\n, command_line);/* Step4. TODO *//* Step5. 创建进程执行 */pid_t id  fork();if (id  0) {/* child *//* Step6: 程序替换 */execvp (command_args[0],  // 保存的是我们要执行的程序名字command_args);exit(1);   // 只要执行到这里子进程一定是替换失败了直接退出。}/* Father */int status  0;pid_t ret  waitpid(id, status, 0);if (ret  0) {   // 等待成功printf(等待成功sig: %d, code: %d\n, status0x7F, (status8)0xFF);}} // end while} 
通过上述代码我们可以创建一个进程来执行用户输入的命令并等待子进程结束。 给命令带颜色 
为了增强Shell的用户体验可以给一些常用命令添加颜色例如 ls 命令 
/* 将接收到的字符串拆开 */
command_args[0]  strtok(command_line, SEP);
int idx  1;
while ((command_args[idx]  strtok(NULL, SEP)));/* 给 ls 命令添加颜色 */
if (strcmp(command_args[0], ls)  0) {command_args[idx]  (char*)--colorauto;
}以上实现了一个简单的Shell具备了基本的提示符显示、用户输入获取、命令解析、内建命令和普通命令的执行功能。 内建命令 环境变量 /* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {chdir(new_path);return 0;  // 调用成功
}int main(void) 
{.../* Step4. TODO 编写后面的逻辑内建命令 */if (strcmp(command_args[0], cd)  0  command_args[1] ! NULL) {ChangeDir(command_args[1]);  // 让调用方进行路径切换continue;}...
}保存环境变量的字符串不能是易变的所以 strcpy mycommand实现与argv的分离 补充vim 文本替换 
如何快速将mycmd换为myshell呢 通过如下操作 : %s/mycmd/myshell/g 就可以啦 细节设置的思考在最后一部分让我们先来看一下整体 
整体代码 
#include stdio.h
#include stdlib.h
#include string.h
#include assert.h
#include unistd.h//创建子进程
#include stdlib.h//这些文件都是什么意思
#include sys/types.h
#include sys/wait.h#define LEFT [
#define RIGHT ]
#define LABLE #
#define DELIM  \t
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode  0;
int quit  0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];//存储切割之后的命令行
char pwd[LINE_SIZE];// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表const char *getusername()
{return getenv(USER);
}const char *gethostname()
{return getenv(HOSTNAME);
}void getpwd()
{getcwd(pwd, sizeof(pwd));//获取当前工作目录
}void interact(char *cline, int size)//交互
{getpwd();printf(LEFT%s%s %sRIGHTLABLE , getusername(), gethostname(), pwd);char *s  fgets(cline, size, stdin);输入流进行输入assert(s);//断言不为空(void)s;//调用s避免报错// abcd\n\0cline[strlen(cline)-1]  \0;//取消自动换行
}// ls -a -l | wc -l | head 
int splitstring(char cline[], char *_argv[])
{int i  0;argv[i]  strtok(cline, DELIM);//区分全局变量和形参_while(_argv[i]  strtok(NULL, DELIM)); // 故意写的//NULL的设置才能实现往后移的切割return i - 1;//去除NULL
}void NormalExcute(char *_argv[])
{pid_t id  fork();if(id  0){perror(fork);return;}else if(id  0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);//系统调用exit(EXIT_CODE);}else{int status  0;pid_t rid  waitpid(id, status, 0);if(rid  id) {//返回正确执行lastcode  WEXITSTATUS(status);}}
}
//切换路径内建命令
//shell执行的内建命令一个一个判断
int buildCommand(char *_argv[], int _argc)
{if(_argc  2  strcmp(_argv[0], cd)  0){chdir(argv[1]);//切换路径的函数getpwd();//获取当前路径sprintf(getenv(PWD), %s, pwd);return 1;}//导入环境变量exportelse if(_argc  2  strcmp(_argv[0], export)  0){strcpy(myenv, _argv[1]);//为什么要进行一个拷贝 是什么意思呢putenv(myenv);//argv 是我们定义的每次都是变化的//所以不要写我们的地址要提字符串的地址return 1;}else if(_argc  2  strcmp(_argv[0], echo)  0){if(strcmp(_argv[1], $?)  0){printf(%d\n, lastcode);//查看退出码lastcode0;}else if(*_argv[1]  $){//打印环境变量char *val  getenv(_argv[1]1);//获取if(val) printf(%s\n, val);}else{printf(%s\n, _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], ls)  0){_argv[_argc]  --color;_argv[_argc]  NULL;}return 0; //带有颜色之后返回再执行普通命令
}int main()
{while(!quit){//1.许多软件启动起来就是死循环// 2. 交互问题,获取命令行, ls -a -l  myfile / ls -a -l  myfile / cat  file.txtinteract(commandline, sizeof(commandline));//对函数做了一下封装// commandline - ls -a -l -n\0 - ls -a -l -n// 3. 子串分割的问题解析命令行int argc  splitstring(commandline, argv);//如何将字串打散呢//strtok需要循环调用//while(argv[i]strtok(commandline,DELIM);//故意写的等号if(argc  0) continue;// 4. 指令的判断 // debug//for(int i  0; argv[i]; i) printf([%d]: %s\n, i, argv[i]);//内键命令本质就是一个shell内部的一个函数int n  buildCommand(argv, argc);// ls -a -l | wc -l// 4.0 分析输入的命令行字符串获取有多少个|, 命令打散多个子命令字符串// 4.1 malloc申请空间pipe先申请多个管道// 4.2 循环创建多个子进程每一个子进程的重定向情况。最开始. 输出重定向, 1-指定的一个管道的写端 // 中间输入输出重定向 0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端// 最后一个输入重定向将标准输入重定向到最后一个管道的读端// 4.3 分别让不同的子进程执行不同的命令--- exec* --- exec*不会影响该进程曾经打开的文件不会影响预先设置好的管道重定向// 5. 普通命令的执行if(!n) NormalExcute(argv);//让命令0的时候执行}return 0;
}重点思考 1.getenv和putenv是什么意思 getenv函数用于获取指定环境变量的值。它的函数定义如下 
char *getenv(const char *name); 参数  name要获取的环境变量的名称。  返回值  如果指定的环境变量存在那么返回一个指向该环境变量值的指针。  如果指定的环境变量不存在则返回NULL。  
以下是一个使用getenv函数的示例 
#include stdio.h
#include stdlib.hint main() {char *path  getenv(PATH);if (path ! NULL) {printf(PATH environment variable: %s\n, path);} else {printf(PATH environment variable not found.\n);}return 0;
} 成功实现对环境变量的调用啦 
putenv函数 
putenv函数用于设置环境变量。它的函数定义如下 
int putenv(char *string); 参数  string形式为namevalue的字符串用于设置具体的环境变量及其值。  返回值  成功时返回0。  失败时返回非零值。  
以下是一个使用putenv函数的示例 
#include stdio.h
#include stdlib.hint main() {char env_str[]  MY_ENVhello_world;if (putenv(env_str)  0) {printf(Environment variable set successfully.\n);} else {perror(putenv);return 1;}char *my_env  getenv(MY_ENV);if (my_env ! NULL) {printf(MY_ENV: %s\n, my_env);} else {printf(MY_ENV environment variable not found.\n);}return 0;
} 注意事项 内存管理  getenv返回的指针指向的是环境变量的值不能直接修改此值否则可能导致未定义行为。  putenv函数参数所指向的字符串在函数调用后仍需存在因为putenv不会复制这个字符串。因此传递给putenv的字符串应始终位于可修改的全局或堆内存中而不是局部变量中。  线程安全性  getenv和putenv函数在某些实现中不是线程安全的特别是当修改同一个环境变量时。建议在多线程环境中使用setenv和unsetenv函数它们是现代C库中提供的线程安全的替代函数。  
总结 
getenv和putenv是C语言中用于获取和设置环境变量的基本函数。通过了解并正确使用它们可以更好地管理进程环境。 2.代码extern char **environ; extern char **environ; 是C语言中的全局变量声明用于访问当前进程的环境变量。为了理解这一行代码我们需要理清以下几个关键概念 
环境变量的存储 
在Unix和类Unix操作系统如Linux中环境变量是一组键值对例如PATH/usr/bin用于向进程传递配置信息。每个环境变量项以字符串的形式存储在一个全局变量数组中。这个数组在进程启动时由操作系统初始化并且每个程序都可以访问和修改它。 
环境变量在内存中的表示 
在内存中环境变量通常表示为一个字符串数组每个字符串保存一个环境变量。例如 
PATH/usr/bin
HOME/home/user
USERuser
... 
这些字符串指针存储在一个全局变量数组中即char **environ。 
extern关键字 
extern关键字用于声明一个全局变量但不定义它。它告诉编译器这个变量是在别处比如另一个源文件或由操作系统提供定义的。因此extern char **environ; 仅仅是一个声明用来告知编译器这个变量在别处已经定义过可以在当前文件中使用它。 
为什么这样写 
在标准C库中environ变量实际上在系统库中已经定义我们只需要在我们的程序中声明一下即可使用。这种方式使我们能够访问和操作环境变量。 
这里是extern char **environ;的具体含义 声明它声明了一个外部变量environ是一个指向字符指针的指针。  外部定义实际的环境变量数组由操作系统初始化并定义在某个系统库中。  全局访问通过这个声明我们可以在任何源文件中访问和操作环境变量。  
示例 
下面是一个具体的例子展示了如何使用environ来访问并打印所有环境变量 
#include stdio.h// 声明外部环境变量数组
extern char **environ;int main(void) {// 指向环境变量数组的指针char **env  environ;// 遍历并打印所有环境变量while (*env) {printf(%s\n, *env);env;}return 0;
} 
就可以成功调用所有环境变量啦 总结 
extern char **environ; 这一行代码的作用是声明一个指针数组用于访问当前进程的环境变量。通过这种方式我们可以在C程序中方便地读取和操作环境变量。 3.内建命令是什么 内建命令是指直接内置在操作系统内核中的一些命令与普通的外部命令外部程序文件不同。这些内建命令是直接由shell解释器如Bash、Zsh等所处理而不需要通过外部文件的方式来执行。这些内建命令通常在操作系统的shell环境中被频繁使用并且执行速度更快因为它们不需要创建新的进程来执行。 
在Unix和类Unix操作系统中通常会有一些内建命令比如cd、echo、exit等。这些命令不需要单独的可执行文件而是直接由shell内核提供支持。当用户在shell中输入这些命令时shell会直接处理它们而不需要通过搜索系统路径来找到可执行文件。 
值得一提的是某些shell也允许用户通过自定义的方式添加新的内建命令这样用户可以根据自己的需求来扩展shell的内建功能。 4.lastcode  WEXITSTATUS(status); 在C语言中WEXITSTATUS(status) 是一个宏用于从wait或waitpid返回的状态信息中提取子进程的退出状态。这个宏主要用于处理子进程的退出状态信息。 
具体来说WEXITSTATUS(status) 用于提取子进程在终止时传递给exit或_exit函数的退出状态。这个宏将状态信息进行适当的位操作以获取子进程的退出状态值。 
一般情况下status 是由wait或waitpid函数返回的子进程状态其中包含了有关子进程终止的信息包括退出状态。通过使用WEXITSTATUS(status)可以将状态转换为子进程的退出状态以便于后续处理和判断子进程的终止情况。 
具体的用法示例如下 
#include sys/types.h
#include sys/wait.h
#include stdio.h
#include unistd.h
#include stdlib.hint main() {pid_t pid;int status;int lastcode;pid  fork();if (pid  0) {perror(fork failed);exit(1);} else if (pid  0) {// This is the child processchar *args[]  {ls, -l, NULL};execvp(args[0], args);} else {// This is the parent processwaitpid(pid, status, 0);//获得了子进程的退出码lastcode  WEXITSTATUS(status);printf(子进程的退出状态是%d\n, lastcode);}return 0;
} 
在这个例子中WEXITSTATUS(status) 会从 status 中提取子进程的退出状态并将其赋值给 lastcode。然后这个退出状态可以被用来进行一些处理比如根据不同的退出状态进行不同的操作。 需要注意的是使用 WEXITSTATUS(status) 的前提是要确保传入的 status 参数是一个子进程终止的状态因为该宏只能提取终止进程的退出状态信息。 5.execvp(_argv[0], _argv);的调用 在代码中execvp(_argv[0], _argv) 是一个执行函数 execvp 的调用用于执行磁盘文件上的程序。这个函数会用指定的程序文件由 _argv[0] 指定来覆盖当前进程的镜像并且用 _argv 数组中的参数替换掉原来的程序参数。 
相对路径执行指令 
路径搜索根据 PATH 环境变量execvp 会在指定路径中查找可执行文件。内存映射找到可执行文件后将其映射到当前进程地址空间。替换镜像用新程序的数据、堆栈、代码段替换当前进程的相应部分。执行新程序从其入口点开始执行覆盖原进程的代码。 
下面是对 execvp 函数调用的解释 
_argv[0] 表示要执行的程序文件的路径或名称。如果是一个程序的名称而没有路径execvp 会在 $PATH 环境变量指定的路径中搜索这个程序。_argv 是一个以空指针结尾的字符串数组用于传递给新程序的命令行参数。数组的第一个元素_argv[0]通常是被执行的程序的名称随后的元素是程序的参数。当调用 execvp 时操作系统会加载并执行指定的程序文件并用 _argv 数组中的参数来替换当前进程的参数。因为默认会在PATH中查询就和系统连接上了如果 execvp 调用成功则当前进程的镜像将被新程序替换并且新程序开始执行。原来的程序代码和数据都会被新程序的代码和数据取代。如果 execvp 调用失败它会返回-1并且当前进程的状态不会改变。 
在简单的C代码中execvp 函数通常与 fork 函数一起使用例如 
#include stdio.h
#include stdlib.h
#include unistd.hint main() {char *_argv[]  {ls, -l, -a, NULL}; // 要执行的命令及参数组成的数组execvp(_argv[0], _argv);  // 在新的程序中执行 ls 命令// 如果执行成功下面的代码不会被执行perror(execvp); // 如果 execvp 失败打印出错误信息return 1;
} 需要注意的是execvp 在执行成功后原进程的代码和数据将会被新进程替换。这就意味着如果 execvp 后面还有代码那么这些代码将不会被执行因为当前的程序已经不再存在。 
实现shell 一行一行的运行先判断是否为内建命令 6._argc_argv _argv是一个字符指针数组用于存储命令和参数。_argc是整型变量用于存储命令和参数的数量。splitstring 函数将命令行字符串分割成多个子字符串存储在 _argv 中并返回子字符串的数量 _argc。NormalExcute 函数使用 _argv 数组创建子进程并执行命令。buildCommand 函数使用 _argv 和 _argc 处理内建命令。