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

这篇具有很好参考价值的文章主要介绍了Linux学习——模拟实现mybash小程序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

bash模拟,Linux系统编程,学习,linux,学习笔记

目录

一,跟正宗的bash见个面

二,实现一个山寨的bash

1.提示符

2.输入命令与回显命令

 3.解析命令

4.执行命令

5.执行逻辑

三,全部代码


一,跟正宗的bash见个面

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

   bash模拟,Linux系统编程,学习,linux,学习笔记

二,实现一个山寨的bash

1.提示符

在图:bash模拟,Linux系统编程,学习,linux,学习笔记

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

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;    
}    

效果:

bash模拟,Linux系统编程,学习,linux,学习笔记

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

2.输入命令与回显命令

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

   这两个函数介绍如下:

bash模拟,Linux系统编程,学习,linux,学习笔记

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

bash模拟,Linux系统编程,学习,linux,学习笔记

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';    
}  

效果:

bash模拟,Linux系统编程,学习,linux,学习笔记

 3.解析命令

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

bash模拟,Linux系统编程,学习,linux,学习笔记

这个函数的参数: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                                                                                                                                                                                   
  }   

效果:分割完成!!!

bash模拟,Linux系统编程,学习,linux,学习笔记

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;
}

   文章来源地址https://www.toymoban.com/news/detail-765570.html

到了这里,关于Linux学习——模拟实现mybash小程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SOCKET编程实现Client/ Server程序:比特洪流协议模拟

      一、引言 使用基本的套接字编程技术,以一对基本的TCP协议通信程序为基础,模拟比特洪流(BitTorrent)的分散传输技术完成一个文件的正确传输,使用标准C语言编程。本实验的目的并不是做一个实用的网络程序,而是更好地理解套接字编程原理和P2P技术,重点在特定条件

    2024年02月04日
    浏览(43)
  • Linux之shell编程(BASH)

    Kernel Linux内核主要是为了和硬件打交道 Shell 命令解释器(command interperter) Shell是一个用C语言编写的程序,他是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。 Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核

    2024年01月18日
    浏览(40)
  • Linux Bash编程man帮助手册

    Linux Bash编程man帮助手册

    2024年01月23日
    浏览(52)
  • Linux centos7 bash编程训练

    训练编写一段代码,打印输出100之内的明7暗7,同时要求每5个数字打印在一行。 此项训练主要是考察for循环的使用,及条件判断表达式的设置和不同写法的应用。 常用的for循环有四种写法(如打印1-100的整数):         for i in $(seq 100);do echo $i;done         for i in `s

    2024年02月07日
    浏览(62)
  • Linux centos7 bash编程小训练

    训练要求: 求比一个数小的最大回文数 知识点: 一个数字正读反读都一样,我们称为回文数,如5、11、55、121、222等。 我们训练用bash编写一个小程序,由我们标准输入一个整数,计算机将显示出一个比这个数小的,最大的回文数。 比一个数小的回文数很多,最大的一个可

    2024年02月11日
    浏览(41)
  • Linux centos7 bash编程(小练习)

    这一个for循环嵌套的小练习,难度不大。提供一种写法,供参考: #!/bin/bash # 文件名:99table.sh # 打印输出九九乘法口诀表 for i in {1..9} do         for ((j=1;j=$i;j++))         do                  echo -ne \\\"$j*$i=$[j*i]t\\\"  #用制表符分隔,表格整齐         done         echo done 编写

    2024年02月10日
    浏览(53)
  • Linux centos7 bash编程——-求质数和

    训练项目 :使用函数求质数和。 定义一个函数IsPrime(),据此判断一个数是否为质数 由用户输入一个整数,求出比此数大的两个最小质数之和。 1.先在键盘上输入一个整数 2.求出比此数大的最小质数 3.再求出比此质数大的另一个最小质数 4.设置计数变量,等于2时退出求质数

    2024年02月10日
    浏览(41)
  • Linux centos7 bash编程(循环与条件判断)

    在编程训练中,循环结构与条件判断十分重要。 根据条件为真、为假,确定是否执行循环。 有时,根据条件的真假结果,决定执行哪些语句,这就是分支语句。 为了训练分支语句与循环语句,我们设计一个案例: 求一组数字:两数平方和为另一数平方 符合条件时,有三个

    2024年02月10日
    浏览(46)
  • Linux centos7 bash编程(break和continue)

    在学习shell知识时,简单编程要从格式入手。 首先学习好单行注释和多行注释。 先学习简单整数的打印输出,主要学习 echo 命令,学习选项-e -n的使用。 下面的练习是常用的两个分支跳转程序: break 和 continue 。 #!/bin/bash # 这是 单行注释 # 单行注释的方法就是在每行行首添加

    2024年02月10日
    浏览(39)
  • linux【网络编程】TCP协议通信模拟实现、日志函数模拟、守护进程化、TCP协议通信流程、三次握手与四次挥手

    Tcp通信模拟实现与Udp通信模拟实现的区别不大,一个是面向字节流,一个是面向数据报;udp协议下拿到的数据可以直接发送,tcp协议下需要创建链接,用文件描述符完成数据的读写 1.1.1 接口认识 1.1.1.1 listen:监听socket 1.1.1.2 accept:获取连接 通信就用accept返回的文件描述符,

    2024年02月06日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包