Linux学习——模拟实现mybash小程序

news2025/1/23 11:15:08

目录

一,跟正宗的bash见个面

二,实现一个山寨的bash

1.提示符

2.输入命令与回显命令

 3.解析命令

4.执行命令

5.执行逻辑

三,全部代码


一,跟正宗的bash见个面

 在这篇文章中,我会写一个myshell小程序。这个小程序其实就是一个解释器。在我的机器上它长这样:

   

二,实现一个山寨的bash

1.提示符

在图:

中。这个提示符的信息可以分为四类:

1.用户名    2.主机名   3.当前地址   4.其他字符

在这个图片里:cq就是用户名  VM-8-9-centos就是主机名   mybash就是当前所在路径。

那我们该如何获取呢?两条路:1.其它字符直接打印  2.用户名等用环境变量获取。代码如下:

 #include<stdio.h>    
#include<stdlib.h>//getenv的头文件    
    
const char* Username()//获取用户名    
{    
  const char* user = getenv("USER");    
  if(user) return user;    
  return "none";    
}    
    
const char* Hostname()//获取主机名    
{    
  const char* host = getenv("HOSTNAME");    
  if(host) return host;    
  return "none";    
}    
    
const char* Pwd()  //获取当前地址  
{    
  const char* pwd = getenv("PWD");                                                                                                                                                         
  if(pwd) return pwd;    
  return "none";    
}    
    
    
int main()    
{    
  printf("[%s@%s %s]#\n",Username(),Hostname(),Pwd());    
  return 0;    
}    

效果:

可以看到我们当前的提示符显示是可以成功的。

2.输入命令与回显命令

    想到输入和显示命令时,我猜很多同学的脑子里第一个想到的便是scanf和printf。但是在这里我们是不能使用scanf的。因为我们在输入命令的时候一定会遇到输入空格的情况,如:ls -a -l命令等等。但是scanf在遇到空格的时候便会停下。所以我们不能使用scanf进行读取数据。所以我们采用gets或者fgets来读取数据。

   这两个函数介绍如下:

gets函数是将键盘上的输入读取到str缓冲区里存起来。

fgets的功能跟gets一样,这三个参数的意思如下:1.str表示存储读取到数据的地方

2.num 存储的最大数据量  。 3.stream表示从何读取(键盘读取则为stdin)。

写出代码如下:

 char buff[1024]\\一般将这个数组定义为全局的
 fgets(buff,sizeof(buff),stdin);                                                                                                                                                          
  buff[strlen(buff)-1] = '\0'; \\将回车符给吞掉  
  printf("%s\n",buff);  

封装函数如下:

void  getCommand()    
{    
  fgets(buff,sizeof(buff),stdin);    
  buff[strlen(buff)-1] = '\0';    
}  

效果:

 3.解析命令

  在这里解析命令的意思便是将一个长字符串以空格为分隔符分割成一个一个短的字符串。比如"ls -a -l"就应该分成"ls" "-a" "-l"。在这里我们要使用到一个字符串分割函数:

这个函数的参数:str表示要分割的字符串  delimiters表示分割符。并且要注意的是,当我的第一次分割成功以后,我后面的连续分割就可以将str用NULL表示。先在写出代码如下:

void splictCommand(char* in,char* out[]) \\注意这里的参数in是buff,out是char* argv。这两个参数都定义在全局   
  {    
    int argc = 0;    
    out[argc++]= strtok(in,SEP);    
  while(out[argc++]= strtok(NULL,SEP));   
 
  #ifdef Debug  \\用来测试  
    for(int i = 0;out[i];i++)    
      printf("%d:%s\n",i,out[i]);    
  #endif                                                                                                                                                                                   
  }   

效果:分割完成!!!

4.执行命令

在完成输入和解析命令以后我们就得来执行命令了。我们如何实现命令的执行呢?

1.创建子进程    2.使用程序替换。

在这里要了解的是,有一些命令是必须要让父进程来执行的。比如:cd export echo等。这些命令叫做内建命令。还有一些命令则不需要由父进程来来执行而是要交由子进程来执行。所以我们得创建子进程。 在执行命令的时候步骤如下:

1.先检查是否是内建命令:若是便执行并且返回一个1。若不是便返回0。

代码:

int dobuildin(char* argv[])
  {
    if(strcmp(argv[0],"cd")== 0)//cd是内建命令
    {
      char* path = NULL;
      if(argv[1] == NULL)  path =getHome();
      else  path = argv[1];
       cd(path);  
       return 1;
    }
    else if(strcmp(argv[0],"export") == 0)//export是内建命令
    {
       if(argv[1]== NULL) return 1; 
       strcpy(enval,argv[1]);
       putenv(enval);
       return 1;
    }
    else if(strcmp(argv[0],"echo")==0)//echo是内建命令
    {                                                                                                                                                                                      
      if(argv[1] == NULL)
      {
        printf("\n");
      }
      else{
       if(argv[1] == NULL) 
       {
         printf("\n");
         return 1;
       }
   if(*(argv[1])=='$'&&strlen(argv[1])>1)
       {
         
          char* val = argv[1]+1;
          if(strcmp(val,"?")==0)
          {
            printf("%d\n",lastcode);
            lastcode = 0;
          }
          else printf("%s\n",getenv(val));
       }
       else
       {
         printf("%s\n",argv[1]);
       }
       return 1;
    }
    }
  
  
    return 0;//不是内建命令便返回0
  
  }

然后才是执行其它命令:

void excute(char* argv[])
  {
     
    pid_t id = fork();//创建子进程
    if(id == 0)//子进程执行程序替换
    {
     execvp(argv[0],argv);
     exit(1);//执行完便可以退出
    }
    else 
    {
      int status = 0;
      pid_t  rid = waitpid(id,&status,0);//等待子进程
      if(rid>0){//等待成功
         lastcode = WEXITSTATUS(status);//获取最后一次的退出码
      }
    }
  }

执行逻辑:

 n = dobuildin(argv);//检查并执行内建命令
 if(n) continue;
 excute(argv);//子进程执行命令

这两个函数的执行顺序如上。如果内建命令执行成功在这一次便可以不再执行下面的普通命令的代码。如果不成功便可以执行下面的普通命令的代码。

5.执行逻辑

int main()
  {
   while(1){   
      char Usercommand[NUM];
      int n  =  getCommand(Usercommand,sizeof(Usercommand));//获取命令
      if(n<=0) continue;
      char* argv[SIZE];
      splictCommand(Usercommand,argv);//将命令打散放到数组中                                                                                                                               
      n = dobuildin(argv);//检查并执行内建命令
      if(n) continue;
      excute(argv);//子进程执行命令
   }
      return 0;
  }

三,全部代码

#include<stdio.h>
#include<stdlib.h>//getenv的头文件
#include<string.h>
#include<unistd.h>//fork的头文件
#include<sys/types.h>//要使用pid_t必须包含的头文件  
#include<wait.h>

#define Debug 1
char buff[1024];
char* argv[64];
char enval[1024];//用来存储全局的环境变量
char cwd[1024];
int lastcode = 0;

#define SEP " "
const char* Username()//获取用户名
{
  const char* user = getenv("USER");
  if(user) return user;
  return "none";
}

const char* Hostname()//获取主机名
{
  const char* host = getenv("HOSTNAME");
  if(host) return host;
  return "none";
}

const char* Pwd()
{
  const char* pwd = getenv("PWD");
  if(pwd) return pwd;
  return ".";
}

 char* getHome()
{
   char*  home = getenv("HOME");
  if(home) return home;
  return(char*) "none";
}

int  getCommand()
{

  printf("[%s@%s %s]#",Username(),Hostname(),Pwd());
  char* str = fgets(buff,sizeof(buff),stdin);
  buff[strlen(buff)-1] = '\0';
  if(str) return strlen(str)-1;
  return -1;
}

void splictCommand(char* in,char* out[])
{
  int argc = 0;
  out[argc++]= strtok(in,SEP);
  while(out[argc++]= strtok(NULL,SEP));
#ifdef Debug
  for(int i = 0;out[i];i++)
    printf("%d:%s\n",i,out[i]);
#endif
}

void cd( char* path)
{
  if(path == NULL)
  {
     path = getHome();
  }
   
  int i= chdir(path);
  printf("%d\n",i);
   char temp[1024];
   getcwd(temp,sizeof(temp));//获取pwd并放到临时变量temp中
   sprintf(cwd,"PWD=%s",temp);将pwd放到全局变量cwd中
   putenv(cwd);//用cwd替换掉PWD内的内容实现改变PWD的目的
}

int  dobuildin( char* argv[])
{
   if(strcmp(argv[0],"cd") == 0)
   {
     char* path = argv[1];      
      cd(path);
      return 1;
   }
   else if(strcmp(argv[0],"export")== 0)
   {
        
        char* val = argv[1];
        if(val == NULL) return 1;
        strcpy(enval,val);
        putenv(enval);
        return 1;
   }
   else if(strcmp(argv[0],"echo")== 0)
   {
     
      if(*argv[1]=='$'&&strlen(argv[1])>1)
      {
        
      char* val = argv[1]+1;//$?,$PATH
      if(strcmp(val,"?")==0)
        {
        printf("%d\n",lastcode);//显示最近一次错误码
        lastcode = 0;
        return 1;
        }    
        else {
           printf("%s\n",getenv(val));
        } 
     }
      else
      {
        printf("%s\n",argv[1]);
      }
      return 1;
   }

   return 0;
}

void excute(char* argv[])
{
  
    pid_t id =fork();
    if(id == 0)//子进程 
    {
      execvp(argv[0],argv);
      exit(1);
    }
  
    else//父进程
    {
      int status = 0;
      pid_t rid = waitpid(id,&status,0);//等待子进程
      if(rid>0){
        lastcode = WEXITSTATUS(status); //获取退出码
      } 

    } 
}

int main()
{
  while(1)
  {
    int n = getCommand();
    if(n<=0) continue;
    splictCommand(buff,argv);
     n =  dobuildin(argv);
    if(n) continue;
    excute(argv); 
    
  }
    return 0;
}

   

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

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

相关文章

问卷调查须避免的错误要点(02):避免逻辑错误与提升数据质量

在上篇文章中&#xff0c;我们讲到了4点在打造客户满意度调查中常犯的4个错误&#xff1a;提问偏颇或模糊不清、提问过多开放性问题、合并不同问题、合并不同问题。而在今天这篇文章中&#xff0c;我们将继续探讨做问卷调查要避免的其他错误要点。如果您也有这方面的需求&#…

大话数据结构-查找-线性索引查找

注&#xff1a;本文同步发布于稀土掘金。 4 线性索引查找 4.1 概述 索引就是把一个关键字与它对应的记录相关联的过程&#xff0c;一个索引由若干个索引项构成&#xff0c;每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。 索引按照结构可分为线性索引、树…

Python Opencv实践 - 简单的AR项目

这个简单的AR项目效果是&#xff0c;通过给定一张静态图片作为要视频中要替换的目标物品&#xff0c;当在视频中检测到图片中的物体时&#xff0c;通过单应矩阵做投影&#xff0c;将视频中的物体替换成一段视频播放。这个项目的所有素材来自自己的手机拍的视频。 静态图片&…

力扣每日一题:2646. 最小化旅行的价格总和(2023-12-06)

力扣每日一题 题目&#xff1a;2646. 最小化旅行的价格总和 日期&#xff1a;2023-12-06 用时&#xff1a;30 m 14 s 时间&#xff1a;8ms 内存&#xff1a;42.98MB 思路&#xff1a;先统计旅行中每个节点路过的次数&#xff08;dfs方法&#xff09;&#xff0c;再计算减半后的…

智能优化算法应用:基于龙格-库塔算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于龙格-库塔算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于龙格-库塔算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.龙格-库塔算法4.实验参数设定5.算法结果6.参…

一个最新国内可用的免费GPT4,Midjourney绘画网站+使用教程

一、前言 ChatGPT GPT4.0&#xff0c;Midjourney绘画&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GPT-4对普…

内核无锁队列kfifo

文章目录 1、抛砖引玉2、内核无锁队列kfifo2.1 kfifo结构2.2 kfifo分配内存2.3 kfifo初始化2.4 kfifo释放2.5 kfifo入队列2.6 kfifo出队列2.7 kfifo的判空和判满2.8 关于内存屏障 1、抛砖引玉 昨天遇到这样一个问题&#xff0c;有多个生产者&#xff0c;多个消费者&#xff0c…

❀My学习Linux命令小记录(14)❀

目录 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 57.whatis指令 58.info指令 59.--help指令 60.uname指令 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 功能说明&#xff1a;查看Linux中的指令帮助。 &#xff08;ps.man命…

一网打尽损失函数和正则化的关系,在损失函数中加入正则化有什么用,如何加入,这里为大家用通俗易懂的例子进行详细解释!(系列1)

文章目录 一、BP神经网络预测中&#xff0c;常见的损失函数是均方误差损失函数MSE二、L2正则化的公式如下&#xff1a;三、 结合MSE和L2正则化的总损失函数公式如下&#xff1a;总结 一、BP神经网络预测中&#xff0c;常见的损失函数是均方误差损失函数MSE 在BP神经网络预测中…

ArcGIS Enterprise on Kubernetes 11.1安装示例

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; 目录 安装前置条件基本安装解压文件生成秘钥执行安装脚本 配置DNS方法一方法…

基于jsp+servlet+mybatis的简易在线选课系统

一.数据库 根据实训题目中给出的表间关系图、数据表结构设计、数据说明等等信息&#xff0c;可创建数据库如下&#xff1a; 1.数据库和表的创建 CREATE DATABASE xuanke; -- 创建用户表 CREATE TABLE USER (username VARCHAR(20) PRIMARY KEY NOT NULL,PASSWORD VARCHAR(20)…

【Python】Python读Excel文件生成xml文件

目录 ​前言 正文 1.Python基础学习 2.Python读取Excel表格 2.1安装xlrd模块 2.2使用介绍 2.2.1常用单元格中的数据类型 2.2.2 导入模块 2.2.3打开Excel文件读取数据 2.2.4常用函数 2.2.5代码测试 2.2.6 Python操作Excel官方网址 3.Python创建xml文件 3.1 xml语法…

[实践总结] Java中读取properties配置文件

读取此key.properties文件 代码实现 import java.io.IOException; import java.io.InputStream; import java.util.Properties;public class PropertyUtils {private static final Properties properties new Properties();static {try (InputStream resourceAsStream Prope…

大数据Hadoop-HDFS_架构、读写流程

大数据Hadoop-HDFS 基本系统架构 HDFS架构包含三个部分&#xff1a;NameNode&#xff0c;DataNode&#xff0c;Client。 NameNode&#xff1a;NameNode用于存储、生成文件系统的元数据。运行一个实例。 DataNode&#xff1a;DataNode用于存储实际的数据&#xff0c;将自己管理…

Java LeetCode篇-深入了解二叉树经典解法(三种方式实现:获取二叉树的最大深度)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 对称二叉树 1.1 判断对称二叉树实现思路 1.2 代码实现&#xff1a;判断对称二叉树 2.0 二叉树的最大深度 2.1 使用递归实现获取二叉树的最大深度思路 2.2 代码实…

Docker-多容器应用

一、概述 到目前为止&#xff0c;你一直在使用单个容器应用。但是&#xff0c;现在您将 MySQL 添加到 应用程序堆栈。经常会出现以下问题 - “MySQL将在哪里运行&#xff1f;将其安装在同一个 容器还是单独运行&#xff1f;一般来说&#xff0c;每个容器都应该做一件事&#x…

浅析基于云的DNS管理与负载均衡技术

伴随企业越来越多地实现应用部署的自动化&#xff0c;他们需要快速、智能的负载均衡&#xff0c;从而支持整个数据中心和云环境的应用要求&#xff0c;保证应用可用性。当聊到到具体实现方式时&#xff0c;DNS负载均衡无疑是其中的重要方法。DNS负载均衡为何在当下如此重要&…

2024年十大最好猫罐头有哪些?2024年10款最好的猫罐头盘点

我发现不少人有这样的困扰&#xff01;买到各种数值都很好的猫罐头后&#xff0c;猫咪一点都不吃。或者是猫咪吃了猫罐头之后&#xff0c;吃了一段时间后就软便身体不舒服。 通过本文&#xff0c;我将与大家盘点2024年10款最好的猫罐头&#xff0c;并提供一些选购猫罐头的小妙招…

基于ssm的网上花店设计论文

摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就很关键。因此鲜花销售信息的…

HarmonyOS学习--创建和运行Hello World

创建和运行Hello World 打开DevEco Studio&#xff0c;在欢迎页单击Create Project&#xff0c;创建一个新工程。根据工程创建向导&#xff0c;选择创建Application应用或Atomic Service元服务。选择“Empty Ability”模板&#xff0c;然后单击Next。关于工程模板的介绍和支持…