C语言模拟实现Liunx操作系统与用户之间的桥梁shell(代码详解)

什么是shell?

Shell(壳)是指命令行界面(CLI)或脚本语言,它为用户提供了与操作系统交互的方式。它是一个程序,从用户那里接收命令,并通过与操作系统内核交互来执行这些命令。Shell充当用户和操作系统之间的中介,允许用户执行各种任务,运行程序,操作文件和目录,并通过脚本实现任务自动化。

在Xshell上使用一下shell

在使用Shell时,您在Shell提示符中键入命令,Shell执行这些命令并返回输出。
下图中红圈中的字符串都是linux系统通过shell来执行的一些命令
在这里插入图片描述

模拟实现shell

思路

主要的程序逻辑就是在main()函数中。通过一个无限循环,不断等待用户输入命令并执行。

  • getUserCommand()获取用户输入的命令字符串。
  • commandSplit()将命令字符串按照分隔符分割成参数列表。
  • doBuildin()检查是否为内建命令,如果是,则执行相应的操作。
    如果不是内建命令,则调用execute()函数执行命令。
  • execute()函数使用fork()创建子进程,在子进程中调用execvp()函数执行命令,父进程等待子进程执行完毕。

这样,代码就完成了一个简单的Shell程序,能够处理用户输入的命令,并执行相应的操作。

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024
#define SIZE 64
#define SEP " "char usercommand[NUM];
char cwd[1024];
char enval[100][100];//测试
int count_enval=0;
int lastcode;const char *getUsername()
{const char *name = getenv("USER");if(name) return name;else return "none";}
const char *getHostname()
{const char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";}
const char *getCwd()
{const char *cwd = getenv("PWD");if(cwd) return cwd;else return "none";}//打印命令行提示符,获取用户命令字符串
int getUserCommand(char *command, int num)
{printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());char *r = fgets(command, num, stdin); // 最终你还是会输入\nif(r == NULL) return -1;// "abcd\n" "\n"command[strlen(command) - 1] = '\0'; // 有没有可能越界?不会return strlen(command);
}//分割字符串
void commandSplit(char *in,char **out)
{int argc=0; out[argc++]=strtok(in,SEP);while(out[argc++]=strtok(NULL,SEP));//for (int i=0;out[i];i++)//{//    printf("%d:%s\n",i,out[i]);//}
}int execute(char *argv[])
{pid_t id=fork();if(id<0){return -1;}if(id==0){execvp(argv[0],argv);exit(1);}else{int status=0;pid_t rid=waitpid(-1,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}return 0;
}void cd(const char* path )
{chdir(path);char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);}//内键情况
int doBuildin(char *argv[])
{if(strcmp(argv[0],"cd")==0){char * path=NULL;if(argv[1]==NULL){path=".";}else  path=argv[1];cd(path);return 1;}else if(strcmp(argv[0],"exprot")==0){if(argv==NULL)return 1;strcpy(enval[count_enval],argv[1]);putenv(enval[count_enval]);count_enval++;return 1;}else if(strcmp(argv[0],"echo")==0){char *val =argv[1]+1;if(strcmp(val,"?")==0){printf("%d\n",lastcode);lastcode=0;}else{printf("%s\n",getenv(val));}return 1;}return 0;
}int main()
{while(1){char *argv[SIZE];//1.打印命令行提示符,获取用户命令字符串int n=getUserCommand(usercommand,sizeof(usercommand));if(n<0)continue;//2.分割字符串commandSplit(usercommand,argv);//3. 内键情况(cd,exprot...)n=doBuildin(argv);if(n) continue;//4.执行对应的命令execute(argv);}return 0;
}

运行结果:

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


代码讲解

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
这些头文件包含了在程序中使用的各种函数和类型的声明。

定义常量和全局变量:#define NUM 1024
#define SIZE 64
#define SEP " "
char usercommand[NUM];
char cwd[1024];
char enval[100][100];//测试
int count_enval=0;
int lastcode;

stdio.h、stdlib.h、string.h、unistd.h、sys/types.h、sys/wait.h:包含了一些标准库函数和系统调用的声明。
NUM和SIZE:定义了一些常量,用于数组的大小。
SEP:定义了命令参数的分隔符。
usercommand[NUM]:存储用户输入的命令字符串。
cwd[1024]:存储当前工作目录的路径。
enval[100][100]:存储环境变量的字符串数组。
count_enval:环境变量计数器。
lastcode:上一个命令的退出状态码。


  • getUsername(), getHostname(), getCwd(): 这些函数用于获取当前用户名、主机名和工作目录。
  • getUserCommand(char *command, int num): 这个函数用于获取用户输入的命令,并返回命令的长度。
  • commandSplit(char *in, char *out[]): 这个函数将输入的命令字符串按照分隔符分割成一个个参数,存储在out数组中。
  • execute(char *argv[]): 这个函数使用fork()创建子进程,并在子进程中执行命令。
  • cd(const char *path): 这个函数用于实现cd命令,改变当前工作目录。
  • doBuildin(char *argv[]): 这个函数用于执行内建命令(如cd、export、echo)。

下面是对每一个函数的详细解释:

getUsername()
const char *getUsername()
{const char *name = getenv("USER");if (name)return name;elsereturn "none";
}

该函数用于获取当前用户名。它通过调用getenv()函数获取环境变量"USER"的值,并返回该值作为用户名。如果环境变量不存在,则返回字符串"none"。


getHostname()
const char *getHostname()
{const char *hostname = getenv("HOSTNAME");if (hostname)return hostname;elsereturn "none";
}

该函数用于获取当前主机名。它通过调用getenv()函数获取环境变量"HOSTNAME"的值,并返回该值作为主机名。如果环境变量不存在,则返回字符串"none"。


getCwd()
const char *getCwd()
{const char *cwd = getenv("PWD");if (cwd)return cwd;elsereturn "none";
}

该函数用于获取当前工作目录。它通过调用getenv()函数获取环境变量"PWD"的值,并返回该值作为工作目录。如果环境变量不存在,则返回字符串"none"。


getUserCommand(char *command, int num)
int getUserCommand(char *command, int num)
{printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());char *r = fgets(command, num, stdin);if (r == NULL)return -1;command[strlen(command) - 1] = '\0';return strlen(command);
}

该函数用于获取用户输入的命令。它首先打印出类似"[username@hostname cwd]#"的提示符,然后使用fgets()函数从标准输入中读取用户输入的命令字符串。读取成功后,函数会去掉字符串末尾的换行符,并返回命令的长度。如果读取失败,函数返回-1。


commandSplit(char *in, char *out[])
void commandSplit(char *in, char *out[])
{int argc = 0;out[argc++] = strtok(in, SEP);while (out[argc++] = strtok(NULL, SEP));
}

该函数将输入的命令字符串按照分隔符进行分割,并将分割得到的参数存储在out数组中。分割过程使用strtok()函数,第一次调用时传入原始字符串和分隔符,后续调用传入NULL和分隔符。分割完成后,out数组中存储了每个参数的地址。函数没有返回值,通过参数传递分割得到的参数列表。


execute(char *argv[])
int execute(char *argv[])
{pid_t id = fork();if (id < 0)return -1;else if (id == 0) // child{execvp(argv[0], argv);exit(1);}else // father{int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){lastcode = WEXITSTATUS(status);}}return 0;
}

该函数用于执行命令。它首先调用fork()函数创建一个子进程,然后在子进程中调用execvp()函数执行指定的命令。如果fork()失败,函数返回-1。如果是子进程,调用execvp()执行命令,并在执行完毕后调用exit(1)退出子进程。如果是父进程,使用waitpid()函数等待子进程的结束,并获取子进程的退出状态码。最后,将子进程的退出码存储在lastcode变量中。


cd(const char* path )
void cd(const char* path )
{chdir(path);char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}

调用chdir()函数改变当前工作目录为传入的路径。
使用getcwd()函数获取当前工作目录的路径,并存储在临时数组tmp中。
使用sprintf()函数将路径格式化为"PWD=路径"的形式,并存储在cwd字符串中。
使用putenv()函数将cwd设置为环境变量。


doBuildin(char *argv[])
//内键情况
int doBuildin(char *argv[])
{if(strcmp(argv[0],"cd")==0){char * path=NULL;if(argv[1]==NULL){path=".";}else  path=argv[1];cd(path);return 1;}else if(strcmp(argv[0],"exprot")==0){if(argv==NULL)return 1;strcpy(enval[count_enval],argv[1]);putenv(enval[count_enval]);count_enval++;return 1;}else if(strcmp(argv[0],"echo")==0){char *val =argv[1]+1;if(strcmp(val,"?")==0){printf("%d\n",lastcode);lastcode=0;}else{printf("%s\n",getenv(val));}return 1;}return 0;
}

根据传入的参数数组argv[]判断是否是内键命令。
如果是cd命令,判断是否提供了路径参数,如果没有,则将路径设置为当前目录;否则,使用cd()函数切换到指定路径。
如果是export命令,判断是否提供了参数,如果没有,则返回;否则,将参数复制到enval数组中,并使用putenv()函数将其设置为环境变量,同时递增count_enval计数器。
如果是echo命令,从参数中提取变量名,并判断变量名是否为"?",如果是,则打印上一个命令的退出状态码;否则,使用getenv()函数获取指定变量的值,并打印出来。
如果是以上内键命令之一,返回1;否则,返回0。



int main()
{while(1){char *argv[SIZE];//1.打印命令行提示符,获取用户命令字符串int n=getUserCommand(usercommand,sizeof(usercommand));if(n<0)continue;//2.分割字符串commandSplit(usercommand,argv);//3. 内键情况(cd,exprot...)n=doBuildin(argv);if(n) continue;//4.执行对应的命令execute(argv);}return 0;
}

main()函数:
进入一个无限循环,用于不断接收用户的命令并执行。
定义参数数组argv[SIZE]。
调用getUserCommand()函数获取用户命令字符串。
如果获取失败,则继续下一次循环。
调用commandSplit()函数将命令字符串分割为参数数组argv[]。
调用doBuildin()函数判断是否是内建命令并执行。
如果是内键命令,则继续下一次循环。
调用execute()函数执行命令。
返回0,结束程序运行。

总结

代码实现了一个简单的交互式shell,能够解析用户输入的命令并执行相应的操作。内键命令包括cd切换目录,export设置环境变量,echo打印变量值。其他命令会通过fork()创建子进程并调用execvp()执行外部命令。

(本章完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/151720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CTFHub Git泄露

Log 前言 根据题目描述&#xff0c;这个题目需要使用到工具 GitHack 来完成&#xff0c;而 CTFHub 上提供的工具需要在 python2 环境中执行&#xff0c;注意 python3 环境无法使用。 GitHack准备&#xff08;kali Linux&#xff09; 打开虚拟机 sudo su 以管理员的身份运行…

开源更安全? yum源配置/rpm 什么是SSH?

文章目录 1.开放源码有利于系统安全2.yum源配置&#xff0c;这一篇就够了&#xff01;(包括本地&#xff0c;网络&#xff0c;本地共享yum源)3.rpm包是什么4.SSH是什么意思&#xff1f;有什么功能&#xff1f; 1.开放源码有利于系统安全 开放源码有利于系统安全 2.yum源配置…

Java 11及更高版本的Oracle JDK版本

2021 年 9 月 14 日&#xff0c;Oracle 发布了可以长期支持的 JDK17 版本&#xff0c;那么从 JDK11 到 JDK17&#xff0c;到底带来了哪些特性呢&#xff1f;亚毫秒级的 ZGC 效果到底怎么样呢&#xff1f;值得我们升级吗&#xff1f;而且升级过程会遇到哪些问题呢&#xff1f;带…

【Spring boot】RedisTemplate中String、Hash、List设置过期时间

文章目录 前言Redis中String设置时间的方法Redis中Hash和List设置时间的方法Redis中Hash的put、putAll、putIfAbsent区别 前言 时间类型&#xff1a;TimeUnit import java.util.concurrent.TimeUnit;TimeUnit.SECONDS:秒 TimeUnit.MINUTES&#xff1a;分 TimeUnit.HOURS&…

Javaweb之Ajax的详细解析

1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xff0c;所以…

“移动机器人课程群实践创新的困境与突围”素材

以下是一篇应用型本科教研论文“移动机器人课程群实践创新的困境与突围”的大纲。您可以根据这个大纲展开您的论文写作&#xff1a; 一、引言 移动机器人技术的发展和应用价值移动机器人课程群在应用型本科教育中的重要性论文目的和研究问题&#xff1a;解析移动机器人课程群实…

利用OpenCV做个熊猫表情包 二

之前写了一篇 利用OpenCV做个熊猫表情包吧_Leen的博客-CSDN博客 回想起来觉得有点太弱了&#xff0c;意犹未尽&#xff0c;每次使用需要自己去手动截取人脸&#xff0c;清除黑边什么的才能使用demo去合成表情&#xff0c;无奈之前由于安装的vs&#xff0c;opencv版本都比较低…

扩散模型实战(十):Stable Diffusion文本条件生成图像大模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xff…

【推荐】智元兔AI:一款集写作、问答、绘画于一体的全能工具!

在当今技术飞速发展的时代&#xff0c;越来越多的领域开始应用人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;。其中&#xff0c;AI写作工具备受瞩目&#xff0c;备受推崇。在众多的选择中&#xff0c;智元兔AI是一款在笔者使用过程中非常有帮助的…

Halcon Solution Guide I basics(2): Image Acquisition(图像加载)

文章目录 文章专栏前言文章解读文章开头流程图算子介绍案例自主练习读取一张图片读取多张图片 文章专栏 Halcon开发 Halcon学习 练习项目gitee仓库 前言 今天来看Halcon的第二章&#xff0c;图像获取。在第二章之后&#xff0c;后面文章就会提供案例了。到时候我会尽量完成每一…

场景交互与场景漫游-交运算与对象选取(8-1)

交运算与对象选取 在面对大规模的场景管理时&#xff0c;场景图形的交运算和图形对象的拾取变成了一项基本工作。OSG作为一个场景管理系统&#xff0c;自然也实现了场景图形的交运算&#xff0c;交运算主要封装在osgUtil 工具中在OSG中&#xff0c;osgUtil是一个非常强有力的工…

【Python】给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<=n<=200

2、问题描述 给定一个长度为n的数列&#xff0c;将这个数列按从小到大的顺序排列。1<n<200 样例输入 5 8 3 6 4 9 样例输出 3 4 6 8 9 n int(input()) a list(map(int,input().split())) a.sort() for i in a:print(i,end ) 运行结果&#xff1a;

毕业设计JSP 2384网上diy蛋糕店管理系统【程序源码+讲解视频+调试运行】

一、摘要 本文将介绍一个功能全面、易于使用的网上DIY蛋糕店管理系统。该系统包括用户和管理员两种用户&#xff0c;每种用户都有相应的功能模块。系统实现了网站首页、用户注册/登录、蛋糕展示、综合排行、购物车、蛋糕DIY和用户中心等功能&#xff0c;同时管理员还可以进行管…

庖丁解牛:NIO核心概念与机制详解 01 _ 入门篇

文章目录 Pre输入/输出Why NIO流与块的比较通道和缓冲区概述什么是缓冲区&#xff1f;缓冲区类型什么是通道&#xff1f;通道类型 NIO 中的读和写概述Demo : 从文件中读取1. 从FileInputStream中获取Channel2. 创建ByteBuffer缓冲区3. 将数据从Channle读取到Buffer中 Demo : 写…

算法-二叉树-简单-二叉树的最大和最小深度

记录一下算法题的学习7 二叉树的最大深度 题目&#xff1a;给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 示例分析&#xff…

MATLAB 状态空间设计 —— LQG/LQR 和极点配置算法

系列文章目录 文章目录 系列文章目录前言一、相关函数 —— LQG/LQR 和极点配置算法1.1 LQR —— lqr 函数1.1.1 函数用法1.1.2 举例1.1.2.1 倒摆模型的 LQR 控制 1.2 LQG —— lqg() 函数1.2.1 函数用法1.2.2 举例 1.3 极点配置 —— place() 函数1.3.1 函数用法1.3.2 示例1.3…

Selenium安装WebDriver最新Chrome驱动(含116/117/118/119)

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

如何在虚拟机的Ubuntu22.04中设置静态IP地址

为了让Linux系统的IP地址在重新启动电脑之后IP地址不进行变更&#xff0c;所以将其IP地址设置为静态IP地址。 查看虚拟机中虚拟网络编辑器获取当前的子网IP端 修改文件/etc/netplan/00-installer-config.yaml文件&#xff0c;打开你会看到以下内容 # This is the network conf…

面向开发者的Android

Developerhttps://developer.android.google.cn/?hlzh-cn SDK 平台工具版本说明https://developer.android.google.cn/studio/releases/platform-tools?hlzh-cn#revisions Android SDK Platform-Tools 是 Android SDK 的一个组件。它包含与 Android 平台进行交互的工具…

【Redis】springboot整合redis(模拟短信注册)

要保证redis的服务器处于打开状态 上一篇&#xff1a; 基于session的模拟短信注册 https://blog.csdn.net/m0_67930426/article/details/134420531 整个流程是&#xff0c;前端点击获取验证码这个按钮&#xff0c;后端拿到这个请求&#xff0c;通过RandomUtil 工具类的方法生…