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

设计感十足的网站东营建设信息网(东营市住房和城乡

设计感十足的网站,东营建设信息网(东营市住房和城乡,wordpress文本目录,织梦做网站如何套取别人网站的模板🦄 个人主页: 小米里的大麦-CSDN博客 🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客 🎁 GitHub主页: 小米里的大麦的 GitHub ⚙️ 操作环境: Visual Studio 2022 文章目录从零到一实现一个简易 Shell1. Shell 的基本功能2. 实现 Shell 的提…

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

      • 从零到一实现一个简易 `Shell`
        • 1. `Shell` 的基本功能
        • 2. 实现 `Shell` 的提示符
          • 1. 获取用户信息
          • 2. 定义提示符格式
          • 3. 实现 `interact` 函数
          • 3. 使用 `strtok` 分割字符串
          • 4. 命令执行
          • 5. 内置命令实现
          • 6. 外部命令执行
          • 7. 解析重定向符号
          • 8. 执行重定向
          • 9. 主循环
        • 3. 源码一览
    • 共勉

从零到一实现一个简易 Shell

这应该是个蛮有趣的话题:“什么是 Shell”?相信只要摸过计算机,对于操作系统(不论是 LinuxUnix 或者是 Windows)有点概念的朋友们大多听过这个名词,因为只要有“操作系统“那么就离不开 Shell 这个东西。不过,在讨论 Shell 之前,我们先来了解一下计算机的运行状况吧!举个例子来说:当你要计算机传输出来“音乐”的时候,你的计算机需要什么东西呢?

  1. 硬件:当然就是需要你的硬件有“声卡芯片”这个配备,否则怎么会有声音;
  2. 核心管理:操作系统的核心可以支持这个芯片组,当然还需要提供芯片的驱动程序啰;
  3. 应用程序:需要使用者(就是你)输入发生声音的指令啰!

这就是基本的一个输出声音所需要的步骤!也就是说,你必须要“输入”一个指令之后,“硬件“才会通过你下达的指令来工作!那么硬件如何知道你下达的指令呢?那就是 kernel(核心)的控制工作了!也就是说,我们必须要通过“Shell”将我们输入的指令与 Kernel 沟通,好让 Kernel 可以控制硬件来正确无误的工作!基本上,我们可以通过下面这张图来说明一下:

image-20250422201800429

以上内容摘自《鸟哥的 Linux 私房菜基础学习篇(第四版)》311 页。


1. Shell 的基本功能

一个基本的 Shell 需要具备以下功能:

  • 提示符显示:显示当前用户、主机名和工作目录,例如 [user@host ~]#
  • 命令读取:从标准输入读取用户输入的命令。
  • 命令解析:将输入的命令行分割为命令和参数。
  • 命令执行:支持内置命令(如 cdexport)和外部命令(如 lscat)。
  • 重定向支持:支持输入重定向 <、输出重定向 > 和追加输出重定向 >>
  • 环境变量管理:支持查看和设置环境变量。
  • 退出机制:支持通过 exit 退出 Shell

让我们一步步实现这些功能。

2. 实现 Shell 的提示符

Shell 的提示符是用户交互的起点,通常显示为 [用户名@主机名 当前目录]#。我们需要获取用户名、主机名和当前工作目录。

1. 获取用户信息
  • 用户名:使用 getenv("USER") 获取当前用户名。
  • 主机名:使用 getenv("HOSTNAME") 获取主机名。
  • 当前目录:使用 getcwd() 获取当前工作目录。
2. 定义提示符格式

我们通过宏定义设置提示符的格式:

#define LEFT "["        // 左括号
#define RIGHT "]"       // 右括号
#define LABLE "#"       // 提示符号
3. 实现 interact 函数

interact 函数负责显示提示符并读取用户输入:

void interact(char* cline, int size)
{getpwd();  // 获取当前工作目录printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname1(), pwd);char* s = fgets(cline, size, stdin);  // 读取用户输入assert(s);                            // 确保读取成功(void)s;						    // 显式标记该变量已被“使用”,从而抑制编译器警告(这是一种代码规范技巧,表明有意忽略此变量)cline[strlen(cline) - 1] = '\0';      // 去除末尾换行符check_redir(cline);                   // 检查重定向符号
}
  • getpwd() 调用 getcwd(pwd, sizeof(pwd)) 更新全局变量 pwd

  • printf 格式化输出提示符,例如 [user@host /home]#

  • fgets 从标准输入读取命令行。

  • check_redir 检查是否有重定向符号(稍后实现)。

  • 命令行解析:用户输入的命令行需要被分割成命令和参数。例如,输入 ls -l /home 应分割为 ["ls", "-l", "/home"]

3. 使用 strtok 分割字符串

我们使用 strtok 函数按空格或制表符分割命令行:

#define DELIM " \t"  // 分隔符:空格和制表符int splitstring(char cline[], char* _argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);       // 分割第一个 tokenwhile (_argv[i++] = strtok(NULL, DELIM));  // 继续分割后续 tokenreturn i - 1;                           // 返回参数个数
}
  • strtok(cline, DELIM) 分割第一个 token(命令)。
  • 循环调用 strtok(NULL, DELIM) 获取后续参数。
  • 返回值是参数个数 argc,存储在全局数组 argv 中。

全局变量定义如下:

#define LINE_SIZE 1024
#define ARGC_SIZE 32
char commandline[LINE_SIZE];  // 存储用户输入
char* argv[ARGC_SIZE];        // 存储分割后的参数
4. 命令执行

Shell 需要区分两种命令:

  • 内置命令:由 Shell 直接处理,如 cdexportecho
  • 外部命令:通过 forkexec 执行系统中的可执行文件。
5. 内置命令实现

内置命令在 Shell 进程中直接执行,无需创建子进程。我们在 buildCommand 函数中实现:

int buildCommand(char* _argv[], int _argc)
{// cd 命令:切换目录if (_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);              // 切换工作目录getpwd();sprintf(getenv("PWD"), "%s", pwd);  // 更新 PWD 环境变量return 1;                    // 表示已处理}// export 命令:设置环境变量else if (_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);putenv(myenv);               // 添加到环境变量表return 1;}// echo 命令:打印参数else if (_argc == 2 && strcmp(_argv[0], "echo") == 0){if (strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);  // 打印上一次命令退出码lastcode = 0;}else if (*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);  // 获取环境变量值if (val){printf("%s\n", val);}}else{printf("%s\n", _argv[1]);  // 直接打印参数}return 1;}// 增强 ls 命令if (strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";    // 添加颜色选项_argv[_argc] = NULL;}return 0;  // 未处理,交给外部命令执行
}
  • cd:使用 chdir 切换目录,并更新 PWD 环境变量。
  • export:使用 putenv 设置环境变量,myenv 是全局缓冲区。
  • echo:支持打印上一次退出码、环境变量 VAR 或普通字符串。
  • ls 增强:自动添加 --color 选项以显示彩色输出。
  • 返回值:1 表示内置命令已处理,0 表示需要外部执行。
6. 外部命令执行

外部命令通过 fork 创建子进程并使用 execvp 执行:

void NormalExcute(char* _argv[])
{pid_t id = fork();if (id < 0){perror("fork");return;}else if (id == 0)  // 子进程{int fd = 0;if (rdir == IN_RDIR){fd = open(rdirfilename, O_RDONLY);dup2(fd, 0);  // 重定向标准输入}else if (rdir == OUT_RDIR){fd = open(rdirfilename, O_CREAT | O_WRONLY | O_TRUNC, 0666);dup2(fd, 1);  // 重定向标准输出}else if (rdir == APPEND_RDIR){fd = open(rdirfilename, O_CREAT | O_WRONLY | O_APPEND, 0666);dup2(fd, 1);  // 追加重定向标准输出}execvp(_argv[0], _argv);  // 执行命令exit(EXIT_CODE);          // exec 失败退出}else  // 父进程{int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid == id){lastcode = WEXITSTATUS(status);  // 记录退出码}}
}
  • fork() 创建子进程。

  • 子进程根据重定向类型(rdir)打开文件并使用 dup2 重定向。

  • execvp 执行命令,从 PATH 中查找可执行文件。

  • 父进程使用 waitpid 等待子进程结束,并记录退出码到 lastcode

  • 重定向支持,Shell 支持三种重定向:

    • 输入重定向< filename

    • 输出重定向> filename

    • 追加输出重定向>> filename

7. 解析重定向符号

check_redir 函数中解析重定向:

#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2char* rdirfilename = NULL;  // 重定向文件名
int rdir = NONE;            // 重定向类型void check_redir(char* cmd)
{char* pos = cmd;while (*pos){if (*pos == '>'){if (*(pos + 1) == '>')  // >>{*pos++ = '\0';*pos++ = '\0';while (isspace(*pos)) pos++;rdirfilename = pos;rdir = APPEND_RDIR;break;}else  // >{*pos = '\0';pos++;while (isspace(*pos)) pos++;rdirfilename = pos;rdir = OUT_RDIR;break;}}else if (*pos == '<')  // <{*pos = '\0';pos++;while (isspace(*pos)) pos++;rdirfilename = pos;rdir = IN_RDIR;break;}pos++;}
}
  • 遍历命令行,检测 <>>>
  • 将符号替换为 \0 以分割命令和文件名。
  • 设置全局变量 rdirrdirfilename
  • interact 函数调用 check_redir 进行解析。
8. 执行重定向

NormalExcute 中根据 rdir 处理重定向:

  • 输入重定向:打开文件并重定向到标准输入(文件描述符 0)。
  • 输出重定向:创建或截断文件并重定向到标准输出(文件描述符 1)。
  • 追加输出重定向:创建或追加文件并重定向到标准输出。
  • 环境变量管理:
    • 查看:通过 echo $VAR 查看环境变量值。
    • 设置:通过 export VAR = VALUE 设置环境变量。

这些功能已在 buildCommandechoexport 实现中完成。

9. 主循环

Shell 的主循环负责持续运行:

int main()
{while (!quit){rdirfilename = NULL;  // 重置重定向信息rdir = NONE;interact(commandline, sizeof(commandline));  // 获取输入int argc = splitstring(commandline, argv);   // 解析命令if (argc == 0) continue;int n = buildCommand(argv, argc);            // 处理内置命令if (!n){NormalExcute(argv);                      // 执行外部命令}}return 0;
}
  • 重置重定向状态。
  • 获取并解析用户输入。
  • 处理内置命令或外部命令。
  • quit 变量控制退出(当前代码中未实现 exit 命令,可扩展)。
3. 源码一览
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>// ======================== 宏定义区域 ========================
#define LEFT "["                            // shell 显示用的左括号
#define RIGHT "]"                           // shell 显示用的右括号
#define LABLE "#"                           // shell 显示用的提示符号
#define DELIM " \t"                         // 分隔符(空格和制表符)
#define LINE_SIZE 1024                      // 每一行命令最大长度
#define ARGC_SIZE 32                        // 最大命令参数个数
#define EXIT_CODE 44                        // 子进程执行失败退出码// 重定向类型定义
#define NONE -1
#define IN_RDIR     0                       // 输入重定向(<)
#define OUT_RDIR    1                       // 输出重定向(>)
#define APPEND_RDIR 2                       // 追加输出重定向(>>)// ======================== 全局变量 ========================
int lastcode = 0;                           // 上一次命令的返回码
int quit = 0;                               // 控制是否退出 shell
extern char** environ;                      // 系统环境变量表
char commandline[LINE_SIZE];                // 存储用户输入命令
char* argv[ARGC_SIZE];                      // 存储分割后的命令参数
char pwd[LINE_SIZE];                        // 当前工作目录
char* rdirfilename = NULL;                  // 重定向的文件名
int rdir = NONE;                            // 当前重定向类型char myenv[LINE_SIZE];                      // 存储 export 设置的环境变量// ======================== 工具函数 ========================// 获取当前用户名
const char* getusername()
{return getenv("USER");
}// 获取主机名
const char* gethostname1()
{return getenv("HOSTNAME");
}// 获取当前路径(PWD)
void getpwd()
{getcwd(pwd, sizeof(pwd));
}// 解析重定向符号(< > >>)并设置 rdir 和 rdirfilename
void check_redir(char* cmd)
{char* pos = cmd;while (*pos){if (*pos == '>'){if (*(pos + 1) == '>')          // >> 追加重定向{ *pos++ = '\0';*pos++ = '\0';while (isspace(*pos)){pos++;}rdirfilename = pos;rdir = APPEND_RDIR;break;}else                            // > 普通输出重定向{ *pos = '\0';pos++;while (isspace(*pos)){pos++;}rdirfilename = pos;rdir = OUT_RDIR;break;}}else if (*pos == '<')               // < 输入重定向{*pos = '\0';pos++;while (isspace(*pos)){pos++;}rdirfilename = pos;rdir = IN_RDIR;break;}pos++;}
}// 显示 shell 提示符并获取用户输入命令
void interact(char* cline, int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname1(), pwd);char* s = fgets(cline, size, stdin);    // 获取用户输入assert(s);                              // 确保输入成功(void)s;							 // 显式标记该变量已被“使用”,从而抑制编译器警告(这是一种代码规范技巧,表明有意忽略此变量)cline[strlen(cline) - 1] = '\0';        // 去除换行符check_redir(cline);                     // 检查是否有重定向
}// 将命令行字符串根据空格分割成参数数组
int splitstring(char cline[], char* _argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);while (_argv[i++] = strtok(NULL, DELIM));   // 使用 strtok 循环分割return i - 1;
}// 执行普通命令(fork+exec)
void NormalExcute(char* _argv[])
{pid_t id = fork();if (id < 0){perror("fork");return;}else if (id == 0)                           // 子进程执行命令{int fd = 0;if (rdir == IN_RDIR){fd = open(rdirfilename, O_RDONLY);dup2(fd, 0);                        // 标准输入重定向}else if (rdir == OUT_RDIR){fd = open(rdirfilename, O_CREAT | O_WRONLY | O_TRUNC, 0666);dup2(fd, 1);                        // 标准输出重定向}else if (rdir == APPEND_RDIR) {fd = open(rdirfilename, O_CREAT | O_WRONLY | O_APPEND, 0666);dup2(fd, 1);                        // 标准输出追加重定向}execvp(_argv[0], _argv);                // 执行命令(从 PATH 路径中查找)exit(EXIT_CODE);                        // exec 出错则退出}else                                        // 父进程等待子进程结束{int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid == id){lastcode = WEXITSTATUS(status);     // 记录子进程退出码}}
}// 构建内置命令:cd, export, echo
int buildCommand(char* _argv[], int _argc)
{if (_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);                         // 切换工作目录getpwd();sprintf(getenv("PWD"), "%s", pwd);      // 更新环境变量 PWDreturn 1;}else if (_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);putenv(myenv);                          // 添加或修改环境变量return 1;}else if (_argc == 2 && strcmp(_argv[0], "echo") == 0){if (strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);           // 打印上一次命令的返回码lastcode = 0;}else if (*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);   // 获取环境变量值if (val){printf("%s\n", val);}}else{printf("%s\n", _argv[1]);}return 1;}// 针对 ls 增加颜色选项if (strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color"; // 自动加上颜色显示_argv[_argc] = NULL;}return 0;
}// ======================== 主函数入口 ========================
int main()
{while (!quit){// 初始化重定向信息rdirfilename = NULL;rdir = NONE;// 获取用户输入的命令行interact(commandline, sizeof(commandline));// 分割命令行为参数数组int argc = splitstring(commandline, argv);if (argc == 0) continue;// 处理内置命令int n = buildCommand(argv, argc);// 执行普通命令if (!n){NormalExcute(argv);}}return 0;
}

这个 Shell 虽简单,但展示了 Shell 的相对核心机制。当然还有一些其他功能没有实现,以及代码中还多场景考虑不周到、兼容性、健壮性等问题处理不够完美,还是等以后学深了再完善吧~

共勉

在这里插入图片描述
在这里插入图片描述


文章转载自:

http://dQ8YpAwi.dLphL.cn
http://Maejt7aU.dLphL.cn
http://jUkfBEXg.dLphL.cn
http://prO2SqPf.dLphL.cn
http://QCIeHIJH.dLphL.cn
http://2nv8oVg2.dLphL.cn
http://roZW5qpg.dLphL.cn
http://s6t7k3Q4.dLphL.cn
http://C1btViRM.dLphL.cn
http://7jo8B8ky.dLphL.cn
http://4iRxnYuK.dLphL.cn
http://AZD3INPE.dLphL.cn
http://Lon64bWj.dLphL.cn
http://Ffp2jJnq.dLphL.cn
http://mpKyFdZj.dLphL.cn
http://RpvM6CuC.dLphL.cn
http://BP1TEhjm.dLphL.cn
http://HdsXHQHj.dLphL.cn
http://7Jmo3w8t.dLphL.cn
http://KFkcnJxv.dLphL.cn
http://cJI5XHoV.dLphL.cn
http://Kx8uILCx.dLphL.cn
http://XsXDgCmD.dLphL.cn
http://4Hv6bBPQ.dLphL.cn
http://8klb4TtI.dLphL.cn
http://OrrRRDOV.dLphL.cn
http://SfqSNL5l.dLphL.cn
http://ZCT2ToK5.dLphL.cn
http://c4lsrmSD.dLphL.cn
http://EOwZwkFn.dLphL.cn
http://www.dtcms.com/wzjs/668433.html

相关文章:

  • 电子商务网站开发系统网站建设越来越注重用户体验
  • 东莞 外贸网站建设wordpress 回到首页
  • 网站建设价格很 好乐云seo网站排名查询工具
  • 西安做网站app网站优点介绍
  • 建房子找哪个网站设计怎么知道公司网站是哪个公司做的
  • 做网站现在什么最赚钱吗php手机网站
  • 徐州做网站的公司哪家好中文网站排名
  • 做展示类网站基于mvc的网站开发
  • asp古典网站源码室内设计效果图大全
  • 用dw做的代码怎么放在网站上网站开发手机版
  • 跨境电商网站如何做推广子洲网站建设制作
  • 网站 兼容性微信小程序商城怎么开发
  • 从网站栏目看网站功能商城网站制作公司
  • 宿州市网站建设有哪些公司装修3d效果图怎么制作
  • 广州微网站建设信息wordpress底部热门标签
  • 建设网站的虚拟机配置做网页找什么公司
  • 网站营销概念数据分析师简历
  • 做网站的软件去哪里买桂平网站制作
  • 制作php网站用什么软件竞价恶意点击立案标准
  • 网站免费建设购物网站设计开题报告
  • 剪辑素材网站免费网站可以一个人做吗
  • 做超市商品海报免费海报模版网站seo优化运营
  • 成都建站优化公司电脑店网站模板
  • 在线查询网站收录南昌网络营销网站
  • 重庆网站建设案例开展网络营销的企业网站有哪些
  • 网站制作 南通培训网登录入口
  • 域名注册好后怎么建设网站石家庄企业网站开发
  • 浏览器看外国网站百度关键词排名点
  • 网站广告psd哪个网站做的系统好用
  • 网站建设php文件html文件在线网站建设