Linux--实现简易shell

news2024/11/27 10:40:02

文章目录

  • shell定义和功能
  • myshell.c
    • GetCwd()
    • GetUsrName()
    • GetHostName()
    • MakeCommandLineAndPrint()
    • GetUserCommand()
    • SplitCommand()
    • Die()
    • ExecuteCommand()
    • GetHome()
    • Cd()
    • CheckBuildin()
    • CheckRedir()
    • myshell.c完整代码
  • makefile
  • 测试
  • 函数和进程之间的相似性

Shell是一个功能强大的工具,它既是用户与Linux/Unix系统之间交互的桥梁,也是一种命令语言和程序设计语言。

shell定义和功能

定义:
Shell是一个用C语言编写的程序,它是用户使用Linux/Unix系统的接口。Shell提供了一个界面,通过这个界面,用户可以访问操作系统内核的服务。
功能:
作为命令语言,Shell能够交互式地解释和执行用户输入的命令。
作为程序设计语言,Shell提供了变量定义、赋值、条件判断、循环控制等高级编程功能,用户可以利用这些功能编写Shell脚本来自动化完成复杂的任务。

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
在这里插入图片描述
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

下面我们来实现一个简易的shell

myshell.c

GetCwd()

const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if(cwd==NULL) return "None";
    return cwd;
}

功能:获取当前工作目录的路径。
实现:首先尝试从环境变量PWD中获取当前工作目录的路径。如果PWD不存在,则返回字符串"None"。

GetUsrName()

const char* GetUsrName()
{
    const char* name = getenv("USER");
    if(name==NULL) return "None";
    return name;
}

功能:获取当前用户的用户名。
实现:从环境变量USER中获取当前用户的用户名。如果USER不存在,则返回字符串"None"。

GetHostName()

const char* GetHostName()
{
    const char* name = getenv("HOSTNAME");
    if(name==NULL) return "None";
    return name;
}

功能:获取当前主机的名称。
实现:从环境变量HOSTNAME中获取当前主机的名称。如果HOSTNAME不存在,则返回字符串"None"。

MakeCommandLineAndPrint()

void MakeCommandLineAndPrint()
{
    const char* name = GetUsrName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);
const char* ManageSign = "->";
        const char* CommonSign = ">";

    printf("[%s@%s %s]%s ",name,hostname,cwd[1]=='\0'?"/":cwd+1,
    !strcmp("root",name)?ManageSign:CommonSign);
    fflush(stdout);
}

功能:构建并打印命令行提示符。
实现:结合用户名、主机名和当前工作目录,构建一个命令行提示符,并打印到标准输出。如果用户名是root,则使用特殊的提示符->,否则使用>。

GetUserCommand()

int GetUserCommand()
{
    char* s = fgets(command,sizeof(command),stdin);
    if(s == NULL) return -1;
    int n = strlen(command);
    command[n - 1]='\0';
    return n - 1;
}

功能:从标准输入读取用户输入的命令。
实现:使用fgets从标准输入读取一行文本到command数组中,并去除换行符。如果读取失败,则返回-1。

SplitCommand()

void SplitCommand()
{
    int index = 0;
    gArgv[index++] = strtok(command,SEP);
    while(gArgv[index++] = strtok(NULL,SEP));
}

功能:将用户输入的命令字符串分割成参数数组。
实现:使用strtok函数以空格为分隔符,将command字符串分割成多个参数,并存储在gArgv数组中。

Die()

void Die()
{
    exit(1);
}

功能:终止程序。
实现:调用exit(1)来终止程序,并返回错误码1。

ExecuteCommand()

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
if(id == 0)
    {
        if(filename != NULL)
        {
            if(redir_type == In_Redir)
            {
                int fd = open(filename,O_RDONLY);
                dup2(fd,0);
            }
            else if(redir_type == Out_Redir)
            {
                int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                dup2(fd,1);
            }
            else if(redir_type == App_Redir)
            {
                int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                dup2(fd,1);
            }
            else {}
        }
        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创建一个子进程。在子进程中,根据filename和redir_type处理重定向,然后使用execvp执行命令。如果命令执行失败,则execvp会返回-1,并设置errno,此时子进程通过exit(errno)退出。在父进程中,使用waitpid等待子进程结束,并获取子进程的退出状态。

GetHome()

const char* GetHome()
{
    const char* home = getenv("HOME");
    if(home== NULL) return "/";
    return home;
}

功能:获取用户的主目录路径。
实现:从环境变量HOME中获取用户的主目录路径。如果HOME不存在,则返回根目录"/"。

Cd()

void Cd()
{
    const char* path = gArgv[1];
    if(path == NULL) path = GetHome();
    chdir(path);

    char temp[SIZE*2];
    getcwd(temp, sizeof(temp));
    size_t temp_len = strlen(temp);
if (temp_len > (sizeof(cwd) - 5)) { // 5 for "PWD=" and the null terminator
    temp_len = sizeof(cwd) - 5;
    temp[temp_len] = '\0'; // 确保 temp 是以 null 终止的
}
    snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
    putenv(cwd);

}

功能:改变当前工作目录。
实现:如果gArgv[1](即命令的第二个参数)存在,则尝试将其作为新路径改变当前工作目录。如果gArgv[1]为空,则尝试将HOME环境变量的值作为新路径。成功改变目录后,更新cwd环境变量以反映新的工作目录。

CheckBuildin()

bool CheckBuildin()
{
    int yes = 0;
    if(strcmp("cd",gArgv[0]) == 0)
    {
        yes = 1;
        Cd();
    }
    else if(!strcmp("echo",gArgv[0])&&!strcmp(gArgv[1],"$?"))
    {
        yes=1;
        printf("%d",lastcode);
        lastcode=0;
    }
    return yes;
}

功能:检查命令是否为内建命令,并执行之。
实现:如果命令是cd或echo $?,则执行相应的内建命令。cd命令通过调用Cd函数实现,echo $?命令则打印上一个命令的退出状态。

CheckRedir()

void CheckRedir()
{
    int pos = 0;
    int n = strlen(command);
    while(pos < n)
    {
        if(command[pos]=='>')
        {
            if(command[pos+1]=='>')
            {
                redir_type = App_Redir;
                command[pos++]='\0';
                pos++;
                SkipSpace(command,pos);
                filename = command + pos;
            }
            else
            {
                command[pos++]='\0';
                redir_type = Out_Redir;
                SkipSpace(command,pos);
filename = command + pos;
            }
        }
        else if(command[pos]=='<')
        {
            command[pos++]='\0';
            redir_type = In_Redir;
            SkipSpace(command,pos);
            filename = command + pos;
        }
        else pos++;
    }
}

功能:检查并处理命令中的重定向。
实现:遍历命令字符串,查找重定向符号(>或<),并根据符号的类型(输入重定向、输出重定向、追加重定向)设置redir_type和filename变量。

myshell.c完整代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>

#define NUM 1000
#define SIZE 64
#define SkipPath(p) do{ p+=strlen(p)-1; while(*p!='/') p--;  }while(0)
#define SkipSpace(cmd, pos) do {\
    while(1)\
    {\
        if(isspace(cmd[pos]))\
            pos++;\
        else break;\
    }\
}while(0)

#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3

char cwd[SIZE*2];
char* gArgv[NUM];
char command[NUM];
char* filename = NULL;
const char* SEP =" ";
int redir_type = 0;
int lastcode = 0;

const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if(cwd==NULL) return "None";
    return cwd;
}

const char* GetUsrName()
{
    const char* name = getenv("USER");
    if(name==NULL) return "None";
    return name;
}

const char* GetHostName()
{
    const char* name = getenv("HOSTNAME");
    if(name==NULL) return "None";
    return name;
}

void MakeCommandLineAndPrint()
{
    const char* name = GetUsrName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);
const char* ManageSign = "->";
        const char* CommonSign = ">";

    printf("[%s@%s %s]%s ",name,hostname,cwd[1]=='\0'?"/":cwd+1,
    !strcmp("root",name)?ManageSign:CommonSign);
    fflush(stdout);
}

int GetUserCommand()
{
    char* s = fgets(command,sizeof(command),stdin);
    if(s == NULL) return -1;
    int n = strlen(command);
    command[n - 1]='\0';
    return n - 1;
}

void SplitCommand()
{
    int index = 0;
    gArgv[index++] = strtok(command,SEP);
    while(gArgv[index++] = strtok(NULL,SEP));
}

void Die()
{
    exit(1);
}

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
if(id == 0)
    {
        if(filename != NULL)
        {
            if(redir_type == In_Redir)
            {
                int fd = open(filename,O_RDONLY);
                dup2(fd,0);
            }
            else if(redir_type == Out_Redir)
            {
                int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                dup2(fd,1);
            }
            else if(redir_type == App_Redir)
            {
                int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                dup2(fd,1);
            }
            else {}
        }
        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);
            }
        }
}
}

const char* GetHome()
{
    const char* home = getenv("HOME");
    if(home== NULL) return "/";
    return home;
}

void Cd()
{
    const char* path = gArgv[1];
    if(path == NULL) path = GetHome();
    chdir(path);

    char temp[SIZE*2];
    getcwd(temp, sizeof(temp));
    size_t temp_len = strlen(temp);
if (temp_len > (sizeof(cwd) - 5)) { // 5 for "PWD=" and the null terminator
    temp_len = sizeof(cwd) - 5;
    temp[temp_len] = '\0'; // 确保 temp 是以 null 终止的
}
    snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
    putenv(cwd);

}
bool CheckBuildin()
{
    int yes = 0;
    if(strcmp("cd",gArgv[0]) == 0)
    {
        yes = 1;
        Cd();
    }
    else if(!strcmp("echo",gArgv[0])&&!strcmp(gArgv[1],"$?"))
    {
        yes=1;
        printf("%d",lastcode);
        lastcode=0;
    }
    return yes;
}

void CheckRedir()
{
    int pos = 0;
    int n = strlen(command);
    while(pos < n)
    {
        if(command[pos]=='>')
        {
            if(command[pos+1]=='>')
            {
                redir_type = App_Redir;
                command[pos++]='\0';
                pos++;
                SkipSpace(command,pos);
                filename = command + pos;
            }
            else
            {
                command[pos++]='\0';
                redir_type = Out_Redir;
                SkipSpace(command,pos);
filename = command + pos;
            }
        }
        else if(command[pos]=='<')
        {
            command[pos++]='\0';
            redir_type = In_Redir;
            SkipSpace(command,pos);
            filename = command + pos;
        }
        else pos++;
    }
}
int main()
{
    while(1)
    {
        //命令行
        MakeCommandLineAndPrint();

        //获取用户字符串
        int n = GetUserCommand();
        if(n<=0) continue;

        //检查重定向
        CheckRedir();

        //切割字符
        SplitCommand();

        //内建命令
        n = CheckBuildin();
        if(n) continue;

        //执行命令
        ExecuteCommand();
        filename = NULL;
    }
    return 0;
}

makefile

mytest:myshell.c
        gcc -o $@ $^
.PHONY:clean
clean:
        rm -f mytest

测试

在这里插入图片描述
在这里插入图片描述
make后出现了目标程序mytest*

运行一下
在这里插入图片描述
mytest运行后自主shell出现了,测试一下功能
在这里插入图片描述
ctrl c推出自主shell
在这里插入图片描述

函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的
操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程
序之内的模式扩展到程序之间。如下图:

在这里插入图片描述
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

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

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

相关文章

LVS之net模式实验

总结&#xff1a; lvs #配置环境&#xff0c;两个网卡 [rootlvs ~]# cd /etc/NetworkManager/system-connections/ [rootlvs system-connections]# ls ens160.nmconnection eth0.nmconnection eth1.nmconnection [rootlvs system-connections]# vim eth0.nmconnection [co…

华为OD机试 - 猜数字 - 穷举搜索(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

【RabbitMQ之一:windows环境下安装RabbitMQ】

目录 一、下载并安装Erlang1、下载Erlang2、安装Erlang3、配置环境变量4、验证erlang是否安装成功 二、下载并安装RabbitMQ1、下载RabbitMQ2、安装RabbitMQ3、配置环境变量4、验证RabbitMQ是否安装成功5、启动RabbitMQ服务&#xff08;安装后服务默认自启动&#xff09; 三、安…

Vue2转Vue3学习历程

选项式API>组合式API vue3和vue2的差别就是选项式api改为组件式api&#xff0c;就是以前vue2要定义data、method、mounted&#xff0c;在vue3就变为了更模块化的&#xff0c;并且我感觉vue3设计思路更多是以调用方法的方式实现&#xff0c;比如我实现一个方法&#xff0c;并…

C语言深入理解指针2

1.数组名的理解 #include <stdio.h> int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] %p\n", &arr[0]);printf("arr %p\n", arr);return 0; }可以发现数组名和数组首元素地址的打印结果一样&#xff0c;因此&#xf…

研究生深度学习入门的十天学习计划------第七天

第7天&#xff1a;自然语言处理&#xff08;NLP&#xff09;中的深度学习 目标&#xff1a; 掌握自然语言处理的基础知识与深度学习模型&#xff0c;理解如何应用RNN、LSTM、Transformer等模型处理文本数据。 7.1 自然语言处理的基础概念 自然语言处理&#xff08;NLP&#…

Vue学习笔记 二

4、Vue基础扩展 4.1 插槽 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力在Vue中插槽是很重要的存在,通过插槽,我们可以把父组件中指定的DOM作用到子组件的任意位置,后面我们坐项目用到的组件库比如element-ui,vant-ui都频繁用到的插槽,Vue的插槽主要有…

【hot100篇-python刷题记录】【在排序数组中查找元素的第一个和最后一个位置】

R7-二分查找篇 目录 双指针 二分优化 ps: 思路&#xff1a; 双指针 直接用双指针回缩啊 class Solution:def searchRange(self, nums: List[int], target: int) -> List[int]:ret[-1,-1]left,right0,len(nums)-1while left<len(nums):if nums[left]target:ret[0]…

可解释性与公平性的关系

可解释模型更有可能公平的三个原因 可解释性和公平性似乎是相辅相成的。可解释性涉及理解模型如何进行预测。公平性涉及理解预测是否偏向某些群体。负责任的人工智能框架和机器学习会议始终将这两个特征一起提及。然而&#xff0c;可解释性并不一定意味着公平。 话虽如此&…

[米联客-XILINX-H3_CZ08_7100] FPGA程序设计基础实验连载-26浅谈XILINX FIFO的基本使用

软件版本&#xff1a;VIVADO2021.1 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA 实验平台&#xff1a;米联客-MLK-H3-CZ08-7100开发板 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http…

9、Django Admin优化查询

如果你的Admin后台中有很多计算字段&#xff0c;那么你需要对每个对象运行多个查询&#xff0c;这会使你的Admin后台变得非常慢。要解决此问题&#xff0c;你可以重写管理模型中的get_queryset方法使用annotate聚合函数来计算相关的字段。 以下示例为Origin模型的中ModelAdmin…

Spring6梳理5——基于XML管理Bean环境搭建

以上笔记来源&#xff1a; 尚硅谷Spring零基础入门到进阶&#xff0c;一套搞定spring6全套视频教程&#xff08;源码级讲解&#xff09;https://www.bilibili.com/video/BV1kR4y1b7Qc 目录 ①搭建模块 ②引入配置文件 ③创建BeanXML文件 ④创建Java类文件&#xff08;User…

在K8s上运行GitHub Actions的自托管运行器

1&#xff1a;添加Actions Runner Controller的Helm仓库 helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller helm repo update2&#xff1a;创建GitHub Personal Access Token (PAT) 登录到你的GitHub账户。访…

SQL语句(数据更新、查询操作)

数据库表操作 创建数据库语法格式 create table 表名(字段名1 类型 约束&#xff0c;字段名2 类型 约束&#xff0c;..... ..... )创建学生表&#xff0c;字段要求如下&#xff1a; 姓名&#xff08;长度为10&#xff09;、年龄、身高&#xff08;保留2位小数&#xff09; cre…

安卓shiply热更新入门

目录 一。我的开发环境 二。集成shiply热更新sdk 三。编写代码 1。创建一个CustomRFixLog类 2。创建一个MyApplication类 3。配置AndroidManifest.xml 4。创建一个新的Activity继承AbsRFixDevActivity 用于测试 四。登录shiply后台配置 1。创建项目 五。制作补丁 1。在app…

Ae关键帧动画基础练习-街道汽车超车

目录 1.让背景向左移动 2.让小红车匀速移动 3.实现小黄车的超车 完成街道汽车超车的一个简单动画&#xff0c;背景向左移动看起来就如同画面向右移动了一般&#xff0c;根据这个原理&#xff0c;可以完成这个动画。 导入素材时&#xff0c;要选择不同的图层&#xff0c;这样…

微软AD替代方案统一管理Windows和信创电脑的登录认证与网络准入认证

自国资委79号文明确了2027年底前信息系统全面国产化的目标后&#xff0c;金融单位、央国企集团及各子公司纷纷加大国产化改造力度。不少子、孙公司表示&#xff0c;集团要求到2024年底或2025年底国外的关键IT基础设施要停止使用&#xff0c;如微软AD、云桌面等。 信创国产化是大…

Mybatis链路分析:JDK动态代理和责任链模式的应用

背景 此前写过关于代理模式的文章&#xff0c;参考&#xff1a;代理模式 动态代理功能&#xff1a;生成一个Proxy代理类&#xff0c;Proxy代理类实现了业务接口&#xff0c;而通过调用Proxy代理类实现的业务接口&#xff0c;实际上会触发代理类的invoke增强处理方法。 责任链功…

艾体宝洞察丨透过语义缓存,实现更快、更智能的LLM应用程序

传统的缓存只存储数据而不考虑上下文&#xff0c;语义缓存则不同&#xff0c;它能理解用户查询背后的含义。它使数据访问更快&#xff0c;系统响应更智能&#xff0c;对 GenAI 应用程序至关重要。 什么是语义缓存&#xff1f; 语义缓存解释并存储用户查询的语义&#xff0c;使…

功率谱密度估计(Power Spectral Density Estimation, PSD)介绍,轴承磨损检测

介绍 功率谱密度估计&#xff08;Power Spectral Density Estimation, PSD&#xff09;是信号处理中的一项重要技术&#xff0c;用于描述信号在频率域中的能量分布。PSD提供了信号的功率随频率变化的情况&#xff0c;是分析随机信号和确定信号频率特性的常用工具。 功率谱密度…