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

建行赤峰市分行 - 欢迎访问中国建设银行网站专业网页制作软件能帮助客户组织和管理

建行赤峰市分行 - 欢迎访问中国建设银行网站,专业网页制作软件能帮助客户组织和管理,广州建设局网站首页,网站优化软件方案目录 前言: 一、代码中的核心功能 1. 环境变量获取 2. 当前路径处理 3. 用户输入处理 4. 命令解析 5. 内建命令处理 6. 外部命令执行 7. 错误处理 二、代码中涉及的关键知识点 1. 系统调用 2. 环境变量 3. 字符串处理 4. 文件操作 5. 进程管理 三、…

目录

前言:

一、代码中的核心功能

1. 环境变量获取

2. 当前路径处理

3. 用户输入处理

4. 命令解析

5. 内建命令处理

6. 外部命令执行

7. 错误处理

二、代码中涉及的关键知识点

1. 系统调用

2. 环境变量

3. 字符串处理

4. 文件操作

5. 进程管理

三、代码的运行过程

1. 初始化

2. 用户输入

3. 命令解析

4. 内建命令检测

5. 外部命令执行

6. 循环继续

四、编译与测试

1. 编译

2. 运行

3. 测试

五、源码


前言:

        大家好,今天带大家了解一个简易 Shell 的实现!这个 Shell 是用 C 语言编写的,功能虽然简单,但涵盖了命令行工具的基本要素,如获取用户输入、执行命令、管理环境变量等。接下来,我会详细讲解代码中的各个部分,帮助你理解它是如何工作的。

        我们用下图的时间轴来表示事件的发生次序,其中时间从左向右。shell由标识为bash的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"起建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。

所以要写一个shell,需要循环以下过程:

  • 获取命令行
  • 解析命令行
  • 建立一个子进程(fork)
  • 替换子进程(execvp)
  • 父进程等待子进程退出(wait)

一、代码中的核心功能

1. 环境变量获取

        Shell 需要知道一些系统信息,比如用户名、主机名、当前路径等,这些信息都存储在环境变量中。代码中的 GetUserNameGetHostName GetCwd 函数分别用于获取这些信息:

// 获取当前用户名
const char *GetUserName()
{const char *name = getenv("USER");return name ? name : "None";
}// 获取主机名
const char *GetHostName()
{const char *hostname = getenv("HOSTNAME");return hostname ? hostname : "None";
}// 获取当前工作路径
const char *GetCwd()
{const char *cwd = getenv("PWD");return cwd ? cwd : "None";
}
  • getenv 是一个标准库函数,用于获取环境变量的值。

  • 如果 USER 环境变量不存在,返回默认值 "None"

2. 当前路径处理

Shell 会频繁处理路径信息。代码中定义了 SkipPath 宏和 getcwd 函数:

#define SkipPath(p) do{ (p += strlen(p) - 1); while(*p != '/') p--; }while(0); // 逆向查找路径分隔符'\'// 获取当前工作路径
const char *GetCwd()
{const char *cwd = getenv("PWD");return cwd ? cwd : "None";
}
  • 作用:将指针移动到路径字符串的最后一个'/'位置,从而提取出当前工作目录的名称。

  • 示例"/home/user/demo" → 指针定位到/demo前的'/'

  • 实现原理

    1. 将指针移动到字符串末尾(strlen(p)-1

    2. 逆向查找直到遇到'/'字符

    3. 使用do-while确保至少执行一次

3. 用户输入处理

        Shell 需要从用户那里获取输入命令。代码中的 MakeCommandLineAndPrint GetUserCommand 函数负责这个过程:

// 构造并打印命令行提示符
void MakeCommandLineAndPrint()
{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 GetUserCommand(char command[], size_t n)
{char *s = fgets(command, n, stdin);if(s == NULL) return -1;command[strlen(command)-1] = ZERO;return strlen(command);
}
  • MakeCommandLineAndPrint 函数生成一个格式化的提示符,显示用户名、主机名和当前路径。

  • GetUserCommand 函数从标准输入读取用户输入,并将其存储在 command 数组中。

4. 命令解析

        用户输入的命令需要被解析成可执行的格式。代码中的 SplitCommand 函数负责将命令字符串分割成命令和参数:

// 命令分割函数
void SplitCommand(char command[], size_t n)
{gArgv[0] = strtok(command, SEP);int index = 1;while(gArgv[index++] = strtok(NULL, SEP));
}
  • strtok 是一个标准库函数,用于按指定分隔符分割字符串。

  • 分割后的命令和参数存储在全局数组 gArgv 中,其中 gArgv[0] 是命令名称,后面的元素是参数。

5. 内建命令处理

        Shell 通常包含一些内建命令,如 cd、echo $?等。代码中的 CheckBuildin Cd 函数实现了这些功能:

// 获取用户HOME目录
const char *GetHome()
{const char *home = getenv("HOME");return home ? home : "/";
}// 切换目录实现
void Cd()
{const char *path = gArgv[1];if(path == NULL) path = GetHome();chdir(path);// 更新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd);
}// 检查内建命令
int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0) {yes = 1;Cd();}/* 可扩展其他内建命令else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0) {yes = 1;printf("%d\n", lastcode);lastcode = 0;}*/return yes;
}
  • CheckBuildin 函数检查用户输入的命令是否是内建命令。

  • Cd 函数实现了 cd 命令,用于切换当前工作目录。

6. 外部命令执行

Shell 还需要支持执行外部命令。代码中的 ExecuteCommand 函数使用了 fork execvp

// 执行外部命令
void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();if(id == 0) { // 子进程execvp(gArgv[0], gArgv);exit(errno);}else { // 父进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0) {lastcode = WEXITSTATUS(status);if(lastcode != 0)printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}}
}
  • fork 创建一个子进程,子进程通过 execvp 执行用户指定的外部命令。

  • 父进程通过 waitpid 等待子进程结束,并检查其退出状态。

  • 执行流程图

7. 错误处理

代码中的 Die 函数用于简单地终止程序:

// 异常退出处理
void Die()
{exit(1);
}

二、代码中涉及的关键知识点

1. 系统调用

  • fork:创建子进程。

  • execvp:执行外部命令。

  • waitpid:等待子进程结束。

2. 环境变量

  • getenv:获取环境变量的值。

  • putenv:设置环境变量的值。

3. 字符串处理

  • strtok:按分隔符分割字符串。

  • snprintf:格式化字符串。

4. 文件操作

  • fgets:从标准输入读取字符串。

5. 进程管理

  • exit:终止进程。

三、代码的运行过程

1. 初始化

  • main 函数启动时,程序进入一个无限循环,等待用户输入。

2. 用户输入

  • MakeCommandLineAndPrint 输出一个格式化的提示符。
  • GetUserCommand 从标准输入读取用户输入。

3. 命令解析

  • SplitCommand 将用户输入的字符串分割成命令和参数。

4. 内建命令检测

  • CheckBuildin 检查用户输入的命令是否是内建命令(如 cd)并执行相应的操作。

5. 外部命令执行

  • 如果命令不是内建命令,ExecuteCommand 通过 forkexecvp 执行外部命令。

6. 循环继续

  • 重复以上步骤,直到用户输入 exit 或程序被强制终止。

四、编译与测试

1. 编译

使用以下命令编译代码:

gcc myshell.c -o myshell

2. 运行

运行生成的可执行文件:

./myshell

3. 测试

五、源码

/***********************************************
* 简易Shell实现(C语言版)
* 功能:基础命令执行、路径切换、状态码保持
* 编译:gcc -o myshell myshell.c -Wall
***********************************************/#include <stdio.h>      // 标准输入输出
#include <unistd.h>     // 系统调用接口(fork, exec等)
#include <stdlib.h>     // 内存管理、环境变量
#include <errno.h>      // 错误码处理
#include <string.h>     // 字符串操作
#include <sys/types.h>  // 进程类型定义
#include <sys/wait.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*3];       // 当前路径环境变量缓冲区
char *gArgv[NUM];       // 命令参数数组(用于execvp)
int lastcode = 0;       // 记录上条命令的退出状态码/*----------------------------------------------
* 函数声明区(按调用顺序排列)
----------------------------------------------*/
void Die(); // 异常终止函数
const char *GetHome(); // 获取用户主目录
const char *GetUserName(); // 获取当前用户名
const char *GetHostName(); // 获取主机名
const char *GetCwd(); // 获取当前工作目录
void MakeCommandLineAndPrint(); // 构造提示符
int GetUserCommand(char cmd[], size_t n); // 获取输入
void SplitCommand(char cmd[], size_t n); // 分割命令
void ExecuteCommand(); // 执行外部命令
void Cd(); // 切换目录实现
int CheckBuildin(); // 内建命令检查/*----------------------------------------------
* [函数实现] 系统信息获取模块
----------------------------------------------*/// 异常退出处理(简化版)
void Die() {exit(1); // 直接退出并返回状态码1
}// 获取用户主目录路径
const char *GetHome() {// 通过HOME环境变量获取const char *home = getenv("HOME");return home ? home : "/"; // 保底返回根目录
}// 获取当前用户名(来自环境变量)
const char *GetUserName() {const char *name = getenv("USER");return name ? name : "None"; // 默认值处理
}// 获取主机名(环境变量方式)
const char *GetHostName() {const char *hostname = getenv("HOSTNAME");return hostname ? hostname : "None";
}// 获取当前工作目录(环境变量缓存值)
const char *GetCwd() {const char *cwd = getenv("PWD");return cwd ? cwd : "None"; 
}/*----------------------------------------------
* [功能模块] 命令行界面处理
----------------------------------------------*/// 构造并显示提示符
void MakeCommandLineAndPrint() {char line[SIZE]; // 行缓冲区// 获取系统信息三元组const char *username = GetUserName();const char *hostname = GetHostName();const char *cwd = GetCwd();// 路径处理:定位到最后一个'/'后的目录名// 示例:/home/user → userSkipPath(cwd); // 构造提示符格式:[user@host dir]~ snprintf(line, sizeof(line), "[%s@%s %s]~ ", username, hostname, // 处理根目录特殊情况(strlen(cwd) == 1) ? "/" : cwd+1); printf("%s", line);    // 输出提示符fflush(stdout);       // 强制刷新确保立即显示
}// 获取用户输入命令
int GetUserCommand(char command[], size_t n) {// 使用fgets获取整行输入(包含换行符)char *s = fgets(command, n, stdin);if(s == NULL) return -1; // 读取失败处理// 替换换行符为字符串终止符// 示例:"ls -l\n" → "ls -l\0"command[strlen(command)-1] = ZERO; return strlen(command); // 返回有效长度
}// 命令分割:将字符串命令解析为参数数组
void SplitCommand(char command[], size_t n) {// 使用strtok进行分割(破坏性操作)gArgv[0] = strtok(command, SEP); // 首次调用int index = 1;// 循环分割直到返回NULL(自动添加NULL结尾)// 示例:"ls -l" → ["ls", "-l", NULL]while((gArgv[index++] = strtok(NULL, SEP))); 
}/*----------------------------------------------
* [核心功能] 命令执行模块
----------------------------------------------*/// 执行外部命令(非内建命令)
void ExecuteCommand() {pid_t id = fork(); // 创建子进程if(id < 0) Die();  // fork失败则终止if(id == 0) { // 子进程执行流// 使用execvp执行命令(自动搜索PATH)// 参数格式要求:数组以NULL结尾execvp(gArgv[0], gArgv);// 只有exec失败才会执行到这里exit(errno); // 返回错误码} else { // 父进程执行流int status = 0;// 等待子进程结束(阻塞方式)pid_t rid = waitpid(id, &status, 0);if(rid > 0) {// 解析子进程退出状态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(); // 默认主目录// 系统调用切换目录chdir(path);// 更新环境变量(使PWD与真实路径同步)char temp[SIZE*2];getcwd(temp, sizeof(temp)); // 获取实际路径// 构造环境变量字符串(格式:PWD=...)snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // 修改环境变量
}// 检查并执行内建命令
int CheckBuildin() {int yes = 0;const char *enter_cmd = gArgv[0];// cd命令处理if(strcmp(enter_cmd, "cd") == 0) {yes = 1;    // 标记为已处理Cd();       // 调用cd实现}/* 可扩展区域:其他内建命令示例else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0) {yes = 1;printf("%d\n", lastcode); // 输出上条命令状态码lastcode = 0;            // 重置状态码}*/return yes; // 返回是否处理了内建命令
}/*----------------------------------------------
* 主程序入口
----------------------------------------------*/
int main() {int quit = 0; // 退出标志(未实现退出命令)// REPL循环(Read-Eval-Print Loop)while(!quit) {// 步骤1:显示提示符MakeCommandLineAndPrint();// 步骤2:获取用户输入char usercommand[SIZE]; // 输入缓冲区int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1; // 输入错误处理// 步骤3:分割命令参数SplitCommand(usercommand, sizeof(usercommand));// 步骤4:处理内建命令if(CheckBuildin()) continue;// 步骤5:执行外部命令ExecuteCommand();}return 0;
}

文章转载自:

http://4dwtTDf2.fwzjs.cn
http://jIeIRanV.fwzjs.cn
http://CdoNV9Za.fwzjs.cn
http://8QDdOMDd.fwzjs.cn
http://P6Luytcj.fwzjs.cn
http://MmqPsF20.fwzjs.cn
http://ct8n5ZrO.fwzjs.cn
http://p4uTLHKJ.fwzjs.cn
http://jm9EaH5w.fwzjs.cn
http://e8OUsLJU.fwzjs.cn
http://Ieknml9i.fwzjs.cn
http://ueIXTAgY.fwzjs.cn
http://iLF0H7M2.fwzjs.cn
http://AMzl5e6I.fwzjs.cn
http://RiLfz8Pw.fwzjs.cn
http://90mVfE4D.fwzjs.cn
http://rrdOOJyB.fwzjs.cn
http://kF8QunIN.fwzjs.cn
http://ew58SWz0.fwzjs.cn
http://fpUsQf1D.fwzjs.cn
http://FQLTYe4O.fwzjs.cn
http://JVWb9Qw8.fwzjs.cn
http://puXdCngB.fwzjs.cn
http://xK7hkdoy.fwzjs.cn
http://KHefT9Wf.fwzjs.cn
http://FgNlvafL.fwzjs.cn
http://o58KMSjy.fwzjs.cn
http://gJaio0vt.fwzjs.cn
http://YyO2pgCw.fwzjs.cn
http://gHTFkYMx.fwzjs.cn
http://www.dtcms.com/wzjs/663998.html

相关文章:

  • 重庆 网站 建设 公司龙胜做网站的公司
  • 企业网站自助建上海南建设培训执业中心网站
  • 建设公司网站需要准备哪些材料广告创意设计论文
  • 所得税 网站建设费企业网站建设综合实训心得
  • 新民电子网站建设哪家好网站关键词免费优化
  • 怎么在搜狐快站上做网站网页设计页面大小
  • 网站建设怎么分析市场鄂尔多斯市建设厅网站
  • 阿里巴巴网站装修怎么做全屏大图南京网站设计公司兴田德润放心
  • aspcms园林绿化工程网站源码wordpress 发音
  • 做淘宝券网站硅谷主角刚开始做的是软件还是网站
  • 泰安网站制作公司电话深圳手机网站定制开发
  • 做网站公司属于什么行业软件技术方案
  • 网站开发公司怎么做账百度注册页面
  • 建立网站找什么公司中小企业名录库
  • 做网站业务的怎么寻找客户网站轮播动态图如何做
  • 50万做网站网站栏目设置完整度建设
  • 建设工程消防网站进入程序wordpress portfolio插件
  • 网站建设需求流程图cytoscape网站开发
  • 学做网站需要懂什么软件wordpress 中文用户
  • 做简单的网站首页企业如何在网站做认证
  • 公关网站建设深圳平面设计
  • 交做网站视频百度云订阅号申请
  • 网站建设特色网站更换域名备案吗
  • 用自己的电脑做网站套用别人产品图片做网站
  • 广州建设厅网站首页有后台的网站如何建设
  • wdcp创建网站怎么给自己做个网站吗
  • 三合一网站有必要吗株洲网站优化找哪家
  • 房地产集团网站欣赏做外贸没有网站需要什么条件
  • 微网站建设方案财政龙岩做网站有那几家
  • 云服务器多网站解析弄个微信小程序多少钱