进程控制---自定义Shell

目录

1.自定义Shell

1.打出对应命令行

1.样式上

2.第二版

3.第三版

命令行解析

执行命令

小调整

4.第四版

[chdir 更改工作路径 哪个进程调chdir就更改哪个路径](#chdir 更改工作路径 哪个进程调chdir就更改哪个路径)

内建命令

系统调用

5.echo

shell有两张表,一张命令行参数表,一张环境变量表


1.自定义Shell

user1@iZ5waahoxw3q2bZ myshell\]$ 我们自己有个对应的字符串,shell启动时也可以打出类似形式的字符串 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.c #include int main() { printf("[user1@iZ5waahoxw3q2bZ myshell]#"); return 0; } ![](https://i-blog.csdnimg.cn/direct/95681f2429c0420ab4cd23c03c89c2f9.png) **获取指定环境变量的值** #include char *getenv(const char *name); ### 1.打出对应命令行 #### 1.样式上 #[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.c #include #include #include 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } int main() { printf("[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd()); return 0; } > \[user1@iZ5waahoxw3q2bZ myshell\]$ ./myshell > > \[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell\]#\[user1@iZ5waahoxw3q2bZ myshell\]$ > \[user1@iZ5waahoxw3q2bZ myshell\]$ > > 系统卡在这里是在等用户输入,我们想让自己的命令行也可以这样 如果是用scanf会把我们这个读成三部分 比如我们输入ls -a -l在shell看来时"ls" "-a" "-l"字符串。 [user1@iZ5waahoxw3q2bZ myshell]$ mv myshell.c myshell.cc 搞成C跟C++混编 [user1@iZ5waahoxw3q2bZ myshell]$ cat Makefile myshell:myshell.cc g++ -o $@ $^ #-std=c99 .PHONY:clean clean: rm -f myshell fgets char *fgets(char *s, int size, FILE *stream); 缓冲区大小 C标准可以从指定的文件流中获取信息,按行获取 成功了就是s的地址,失败就是NULL。FILE \*标准输入 为了避免将回车搞成多一行加入一行commandline\[strlen(commandline)-1\]=0;//清理\\n [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #define COMMAND_SIZE 1024 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } int main() { printf("[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd()); char commandline[COMMAND_SIZE]; char *c = fgets(commandline,sizeof(commandline),stdin); if(c==NULL) return 1; commandline[strlen(commandline)-1]=0;//清理\n printf("echo %s\n",commandline); return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ clear [user1@iZ5waahoxw3q2bZ myshell]$ make make: `myshell' is up to date. [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]#ls -a-^H ^C [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]#ls -a -l echo ls -a -l **int snprintf(char \*str, size_t size, const char \*format, ...);** 安全的格式化输出函数,用于将格式化字符串写入字符数组,并**限制最大写入长度**,防止缓冲区溢出。 > * **`snprintf`** 是 C 标准库函数,用于格式化字符串并写入缓冲区,同时保证不会超过缓冲区大小(防止溢出)。 > > * **`cmd_prompt`**:输出缓冲区(字符数组)。 > > * **`size`** :缓冲区总大小,`snprintf` 最多写入 `size-1` 个字符,末尾自动加 `'\0'`。 > > * **`FORMAT`** :通常定义为类似 `"%s@%s:%s$ "` 的格式字符串,包含三个 `%s` 占位符。 > > * **`GetUserName()`**:返回当前用户名(字符串)。 > > * **`GetHostName()`**:返回主机名(字符串)。 > > * **`GetPwd()`**:返回当前工作目录路径(字符串)。 #### 2.第二版 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); } return 0; } ![](https://i-blog.csdnimg.cn/direct/e6cb943005a24735b43c07bc24371980.png) #### 3.第三版 命令行参数在进行输出的时候,命令行参数这个表是由shell自己维护的 命令行分析 "ls -a -l" -\> "ls" "-a" "-l",我们可以用一个接口去分割 strtok 字符串切割 > SYNOPSIS > > #include \ > > char \*strtok(char \*str, const char \*delim); ##### 命令行解析 [user1@iZ5waahoxw3q2bZ myshell]$ cat Makefile myshell:myshell.cc g++ -o $@ $^ -std=c++11 #-std=c99 .PHONY:clean clean: rm -f myshell [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); return true; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" CommandParse(commandline); PrintArgv(); } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ make g++ -o myshell myshell.cc -std=c++11 #-std=c99 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a -l echo ls -a -l argv[0]->ls argv[1]->-a argv[2]->-l argc:4 为什么这里是4呢?因为把NULL也算上了。加上一行g_argc--; [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return true; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" CommandParse(commandline); PrintArgv(); } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ make g++ -o myshell myshell.cc -std=c++11 #-std=c99 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a -l echo ls -a -l argv[0]->ls argv[1]->-a argv[2]->-l argc:3 已经按照对应的需求把命令行解析完了 这也符合曾经讲的shell提供两张表 ##### 执行命令 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return true; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" CommandParse(commandline); //PrintArgv(); //4.执行命令 pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } //father pid_t rid = waitpid(id,nullptr,0); (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ make g++ -o myshell myshell.cc -std=c++11 #-std=c99 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a -l echo ls -a -l total 32 drwxrwxr-x 2 user1 user1 4096 Apr 28 20:23 . drwxrwxr-x 3 user1 user1 4096 Apr 28 17:28 .. -rw-rw-r-- 1 user1 user1 90 Apr 28 20:02 Makefile -rwxrwxr-x 1 user1 user1 13904 Apr 28 20:23 myshell -rw-rw-r-- 1 user1 user1 2499 Apr 28 20:23 myshell.cc [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd echo pwd /home/user1/linux-learning/linux/26-4-29/myshell 再改地优雅点 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return true; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } //father pid_t rid = waitpid(id,nullptr,0); (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 return 0; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" CommandParse(commandline); //PrintArgv(); //4.执行命令 Execute(); } return 0; } 我们发现我们的命令都是带很长的路径的,修改一下 ##### 小调整 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } // / /a/b/c std::string DirName(const char *pwd) { #define SLASH "/" std::string dir = pwd; if(dir == SLASH) return SLASH; auto pos = dir.rfind(SLASH);//逆向找 if(pos == std::string::npos) return "BUG?"; return dir.substr(pos+1);//+1是为了不想加上前面/ } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { 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); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return true; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } //father pid_t rid = waitpid(id,nullptr,0); (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 return 0; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" CommandParse(commandline); //PrintArgv(); //4.执行命令 Execute(); } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ make g++ -o myshell myshell.cc -std=c++11 #-std=c99 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ myshell]# ls -a -l echo ls -a -l total 32 drwxrwxr-x 2 user1 user1 4096 Apr 28 20:39 . drwxrwxr-x 3 user1 user1 4096 Apr 28 17:28 .. -rw-rw-r-- 1 user1 user1 90 Apr 28 20:02 Makefile -rwxrwxr-x 1 user1 user1 14840 Apr 28 20:39 myshell -rw-rw-r-- 1 user1 user1 2827 Apr 28 20:39 myshell.cc [user1@iZ5waahoxw3q2bZ myshell]# pwd echo pwd #### 4.第四版 \[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell\]# cd .. \[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell\]# 我们发现输入cd ..路径没有改变 为什么不变化呢? **因为目前的命令都是让子进程去执行的!cd真正要更改的是bash的路径。所以遇到cd这样的命令不能让子进程去执行,应该让父进程自己亲自执行然后把自己切掉。这种命令称之为内建命令** 内建命令检查 > bool CheckAndExecBuiltin() > > { > > std::string cmd = g_argv\[0\]; > > if(cmd == "cd") > > { > > return true; > > } > > return false; > > } > > //4.检测并执行内建命令 > > if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 > > continue; > CommandParse中 return g_argc \> 0 ? true : false;**向调用者返回命令解析是否成功** if(!CommandParse(commandline)) continue;保证代码健壮性 ##### chdir 更改工作路径 哪个进程调chdir就更改哪个路径 > **NAME** > > **chdir, fchdir - change working directory** > > **SYNOPSIS** > > **#include \** > > **int chdir(const char \*path);** > > **int fchdir(int fd);** 进程先变,然后就是环境变量 要执行内建命令要bash自己去执行 ##### 内建命令 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; 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 = getenv("PWD"); return pwd == NULL ? "None" : pwd; } const char *GetHome()//获取家目录 { const char *home = getenv("HOME"); return home == NULL ? "" : home; } // / /a/b/c std::string DirName(const char *pwd) { #define SLASH "/" std::string dir = pwd; if(dir == SLASH) return SLASH; auto pos = dir.rfind(SLASH);//逆向找 if(pos == std::string::npos) return "BUG?"; return dir.substr(pos+1);//+1是为了不想加上前面/ } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str()); snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return g_argc > 0 ? true : false; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } bool CheckAndExecBuiltin() { std::string cmd = g_argv[0]; if(cmd == "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];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where //cd - /cd ~ //但是还有类似这两个这种不支持 if(where == "-") { //TODO } else if(where == "~") { //TODO } else { chdir(where.c_str()); } } return true; } return false; } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } //father pid_t rid = waitpid(id,nullptr,0); (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 return 0; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; //printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); //4.检测并执行内建命令 if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 continue; //5.执行命令 Execute(); } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd /home/user1/linux-learning/linux/26-4-29/myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# cd [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd /home/user1 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# cd / [user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd / 但是我们发现路径表示没有改变 所以其实当进程或者shell路径发生变化时,一定是进程路径先变才更新的环境变量。 所以不用环境变量去获取,用getcwd(); ##### 系统调用 **getcwd 获得当前进程的工作路径** > **NAME** > > **getcwd, getwd, get_current_dir_name - get current working directory** > > **SYNOPSIS** > > **#include \** > > **char \*getcwd(char \*buf, size_t size);** > > **char \*getwd(char \*buf);** > > **char \*get_current_dir_name(void);** [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; //for test char cwd[1024]; char cwdenv[1024]; 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 = getenv("PWD"); const char *pwd = getcwd(cwd,sizeof(cwd)); if(pwd != NULL) { snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd); putenv(cwdenv); //return "None"; } return pwd == NULL ? "None" : pwd; } const char *GetHome()//获取家目录 { const char *home = getenv("HOME"); return home == NULL ? "" : home; } //command bool Cd() { ///std::string where; if(g_argc == 1) { std::string home = GetHome(); if(home.empty()) return true; chdir(home.c_str()); } else { std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where //cd - /cd ~ //但是还有类似这两个这种不支持 if(where == "-") { //TODO } else if(where == "~") { //TODO } else { chdir(where.c_str()); } } return true; } // / /a/b/c std::string DirName(const char *pwd) { #define SLASH "/" std::string dir = pwd; if(dir == SLASH) return SLASH; auto pos = dir.rfind(SLASH);//逆向找 if(pos == std::string::npos) return "BUG?"; return dir.substr(pos+1);//+1是为了不想加上前面/ } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str()); //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return g_argc > 0 ? true : false; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } bool CheckAndExecBuiltin() { std::string cmd = g_argv[0]; if(cmd == "cd") { Cd(); return true; } return false; } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } //father pid_t rid = waitpid(id,nullptr,0); (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 return 0; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; //printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); //4.检测并执行内建命令 if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 continue; //5.执行命令 Execute(); } return 0; } #### 5.echo **int status = 0;//退出信息** 其实echo命令也是一条内建命令 linux中有些命令可能既属于内建又属于独立的命令,说明那些执行两份,shell执行一份,进程执行一份 [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; //for test char cwd[1024]; char cwdenv[1024]; //last exit code 最后退出码 int lastcode=0; 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 = getenv("PWD"); const char *pwd = getcwd(cwd,sizeof(cwd)); if(pwd != NULL) { snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd); putenv(cwdenv); //return "None"; } return pwd == NULL ? "None" : pwd; } const char *GetHome()//获取家目录 { const char *home = getenv("HOME"); return home == NULL ? "" : home; } //command bool Cd() { ///std::string where; if(g_argc == 1) { std::string home = GetHome(); if(home.empty()) return true; chdir(home.c_str()); } else { std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where //cd - /cd ~ //但是还有类似这两个这种不支持 if(where == "-") { //TODO } else if(where == "~") { //TODO } else { chdir(where.c_str()); } } return true; } // / /a/b/c std::string DirName(const char *pwd) { #define SLASH "/" std::string dir = pwd; if(dir == SLASH) return SLASH; auto pos = dir.rfind(SLASH);//逆向找 if(pos == std::string::npos) return "BUG?"; return dir.substr(pos+1);//+1是为了不想加上前面/ } void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline { snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str()); //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt,sizeof(prompt)); printf("%s",prompt); fflush(stdout); } bool GetCommandLine(char *out,int size) { //ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return g_argc > 0 ? true : false; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } bool CheckAndExecBuiltin() { std::string cmd = g_argv[0]; if(cmd == "cd") { Cd(); return true; } else if(cmd == "echo") { if(g_argc==2)//要有两个命令行参数 { //echo "hello world" //echo $? //echo $PATH std::string opt = g_argv[1]; if(opt == "$?") { std::cout<< lastcode <0) { lastcode=WEXITSTATUS(status); } return 0; } int main() { while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; //printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); //4.检测并执行内建命令 if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 continue; //5.执行命令 Execute(); } return 0; } [user1@iZ5waahoxw3q2bZ myshell]$ make g++ -o myshell myshell.cc -std=c++11 #-std=c99 [user1@iZ5waahoxw3q2bZ myshell]$ ./myshell [user1@iZ5waahoxw3q2bZ myshell]# echo $PATH /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/user1/.local/bin:/home/user1/bin [user1@iZ5waahoxw3q2bZ myshell]# echo $? 0 #### **shell有两张表,一张命令行参数表,一张环境变量表** **environ** 是一个**指向环境变量数组的全局指针** ,在 C/C++(Unix/Linux)中可用。它定义在 `` 中(POSIX),允许程序直接访问整个环境变量列表。 > **SYNOPSIS > #include \** > > **extern char \*\*environ;** [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; //环境变量表 #define MAX_ENVS 100 char *g_env[MAX_ENVS]; int g_envs = 0; //for test char cwd[1024]; char cwdenv[1024]; //last exit code 最后退出码 int lastcode=0; 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 = getenv("PWD"); const char *pwd = getcwd(cwd,sizeof(cwd)); if(pwd != NULL) { snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd); putenv(cwdenv); //return "None"; } return pwd == NULL ? "None" : pwd; } const char *GetHome()//获取家目录 { const char *home = getenv("HOME"); return home == NULL ? "" : home; } void InitEnv()//初始化一个用于保存环境变量的全局数据结构 { extern char **environ;//声明一个环境变量所对应的信息 memset(g_env,0,sizeof(g_env)); g_envs=0; //本来要从配置文件来 //1.获取环境变量 for(int i=0;environ[i];i++) { //申请空间 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 test 为了验证是我们自己的环境变量,如果有就说明是 g_env[g_envs] = NULL; //2.导成环境变量 for(int i =0; g_env[i];i++) { putenv(g_env[i]); } } //command bool Cd() { ///std::string where; if(g_argc == 1) { std::string home = GetHome(); if(home.empty()) return true; chdir(home.c_str()); } else { std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where //cd - /cd ~ //但是还有类似这两个这种不支持 if(where == "-") { //TODO } else if(where == "~") { //TODO } else { chdir(where.c_str()); } } return true; } void Echo() { if(g_argc==2)//要有两个命令行参数 { //echo "hello world" //echo $? //echo $PATH std::string opt = g_argv[1]; if(opt == "$?") { std::cout<< lastcode < "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return g_argc > 0 ? true : false; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } bool CheckAndExecBuiltin() { std::string cmd = g_argv[0]; if(cmd == "cd") { Cd(); return true; } else if(cmd == "echo") { Echo(); return true; } return false; } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } int status = 0;//退出信息 //father pid_t rid = waitpid(id,&status,0); //(void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 if(rid>0) { lastcode=WEXITSTATUS(status); } return 0; } int main() { // shell启动的时候,需要从系统中获取环境变量 // 我们的环境变量信息应该从父shell统一来 InitEnv(); while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; //printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); //4.检测并执行内建命令 if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 continue; //5.执行命令 Execute(); } //cleanup(); return 0; } 然后make,接着./myshell之后env,发现我们环境变量表里面有这个HAHA=for_test 所以这样也就能明白之前讲的export #### export > **`export` 的核心作用** > > * 将一个变量标记为 **环境变量**,添加到当前 Shell 的环境表中。 > > * 之后通过 `fork` 创建的子进程(例如执行外部命令)会**自动复制**当前的环境变量,从而能访问到这些导出的变量。 #### **alias也是一个内建命令** [user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc #include #include #include #include #include #include #include #include #include #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " //下面是shell定义的全局数据 //1.命令行参数表 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc=0; //2.环境变量表 #define MAX_ENVS 100 char *g_env[MAX_ENVS]; int g_envs = 0; //3.别名映射表 std::unordered_map alias_list; //for test char cwd[1024]; char cwdenv[1024]; //last exit code 最后退出码 int lastcode=0; 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 = getenv("PWD"); const char *pwd = getcwd(cwd,sizeof(cwd)); if(pwd != NULL) { snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd); putenv(cwdenv); //return "None"; } return pwd == NULL ? "None" : pwd; } const char *GetHome()//获取家目录 { const char *home = getenv("HOME"); return home == NULL ? "" : home; } void InitEnv()//初始化一个用于保存环境变量的全局数据结构 { extern char **environ;//声明一个环境变量所对应的信息 memset(g_env,0,sizeof(g_env)); g_envs=0; //本来要从配置文件来 //1.获取环境变量 for(int i=0;environ[i];i++) { //申请空间 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 test 为了验证是我们自己的环境变量,如果有就说明是 g_env[g_envs] = NULL; //2.导成环境变量 for(int i =0; g_env[i];i++) { putenv(g_env[i]); } environ = g_env; } //command bool Cd() { ///std::string where; if(g_argc == 1) { std::string home = GetHome(); if(home.empty()) return true; chdir(home.c_str()); } else { std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where //cd - /cd ~ //但是还有类似这两个这种不支持 if(where == "-") { //TODO } else if(where == "~") { //TODO } else { chdir(where.c_str()); } } return true; } void Echo() { if(g_argc==2)//要有两个命令行参数 { //echo "hello world" //echo $? //echo $PATH std::string opt = g_argv[1]; if(opt == "$?") { std::cout<< lastcode < "ls -a -l\n" 字符串 char *c = fgets(out,size,stdin); if(c==NULL) return false; out[strlen(out)-1]=0;//清理\n if(strlen(out) == 0) return false; return true; } //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc=0; //命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline,SEP); while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP))); g_argc--; return g_argc > 0 ? true : false; } void PrintArgv() { for(int i=0;g_argv[i];i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc:%d\n",g_argc); } 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") { } else if(cmd == "alias") { //std::string nickname = g_argv[i]; //alias_list.insert(k,v); } return false; } int Execute()//执行命令 { pid_t id = fork(); if(id==0) { //child //程序替换 execvp(g_argv[0],g_argv); exit(1); } int status = 0;//退出信息 //father pid_t rid = waitpid(id,&status,0); //(void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告 if(rid>0) { lastcode=WEXITSTATUS(status); } return 0; } int main() { // shell启动的时候,需要从系统中获取环境变量 // 我们的环境变量信息应该从父shell统一来 InitEnv(); while(true) { //1.输出命令行提示符 PrintCommandPrompt(); //2.获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline,sizeof(commandline))) continue; //printf("echo %s\n",commandline); //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); //检测别名 //4.检测并执行内建命令 if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行 continue; //5.执行命令 Execute(); } //cleanup(); return 0; } | 变量 | 含义 | 示例(命令 `ls -a -l`) | |----------|-------------|-----------------------------------------------------------------------------| | `g_argc` | 命令中单词的个数 | `3`("ls"、"-a"、"-l") | | `g_argv` | 指向每个单词的指针数组 | `g_argv[0] → "ls"` `g_argv[1] → "-a"` `g_argv[2] → "-l"` `g_argv[3] → NULL` | **真正执行系统命令的地方是 `Execute()` 函数内部的 `execvp` 调用**。 感谢你的观看,期待我们下次再见!

相关推荐
风曦Kisaki2 小时前
# Linux Shell 编程入门 Day02:条件测试、if 判断、循环与随机数
linux·运维·chrome
李日灐2 小时前
< 6 > Linux 自动化构建工具:makefile 详解 + 进度条实战小项目
linux·运维·服务器·后端·自动化·进度条·makefile
嵌入式×边缘AI:打怪升级日志2 小时前
嵌入式Linux开发:开源组件、第三方库与许可证详解
linux
计算机安禾2 小时前
【Linux从入门到精通】第34篇:搭建FTP与Samba——跨平台文件共享解决方案
linux·运维·服务器
日取其半万世不竭2 小时前
用 Netdata 实时监控服务器,比 Prometheus + Grafana 轻量得多
linux·服务器·网络·系统架构·负载均衡·zabbix·grafana
jamon_tan3 小时前
Linux下cmake构建方法
linux
JiaWen技术圈3 小时前
内核子系统 nf_tables 深度解析
linux·服务器·安全·运维开发
计算机安禾3 小时前
【Linux从入门到精通】第32篇:Nginx入门——高性能Web服务器搭建
linux·服务器·nginx
ZenosDoron3 小时前
Linux 中,rm -r 和 -f
linux·运维·服务器