详解简单的shell脚本 --- 命令行解释器【Linux后端开发】

news2024/11/26 16:22:10


首先附上完整代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
//命令行解释器
//shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令

//保存完整的命令行字符串 -- 充当缓冲区
#define NUM 1024
char cmd_line[NUM];

//保存切割之后的字符串
#define SIZE 32
char* g_argv[SIZE];

#define SEP " "

int main()
{
    //0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环)
    while(1)
    {
        //1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ 
        printf("[dwr@VM-1-1-test shell]$ ");
        fflush(stdout);
       
        //2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"]
        memset(cmd_line, 0, sizeof cmd_line);
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
        {
            continue;
        }
        //输入的回车键设为\0  ls -a -l \n \0
        cmd_line[strlen(cmd_line)-1]  = '\0';

       // printf("echo:%s\n", cmd_line);   //debug
        
        //3.命令行字符串进行解析 "ls -a -l -s"  --->  "la" "-a" "-l" "-s"
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串

        int index = 1;
        while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL
        
        //简单配置ls的颜色
        int i = 1;
        if(strcmp(g_argv[0], "ls") == 0)
        {
            g_argv[i++] = "--color=auto";
        }
       
        //识别别名 - 主要是测试,一般是有接口的
        if(strcmp(g_argv[0], "ll") == 0)
        {
            g_argv[0] = "ls";
            g_argv[i++] = "-l";
            g_argv[i++] = "--color=auto";
        }


        //debug
//        for(index = 0 ; g_argv[index]; index++)
//        {
//            printf("g_argv[%d]: %s\n", index, g_argv[index]);
//        }

        
        //4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令
        //内建命令本质就是shell中的一个函数调用
        if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute;
        {
            if(g_argv[1] != NULL)
            {
                chdir(g_argv[1]); //cd  cd ..
            }

            continue;
        
         }

        //5.创建子进程进行程序替换
        pid_t id = fork();

        //child
        if(id == 0)
        {
           execvp(g_argv[0],g_argv);
           exit(-1);
        }

        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞等待
        if(ret > 0)
        {
            printf("exit code:%d\n",WEXITSTATUS(status));
        }

    }

    return 0;
}

效果:

命令行解释器  

       
shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令

步骤1. 命令行解释器,一定是一个常驻内存的进程 --- 这意味着它是不退出的(死循环)

代码示例:

#include <stdio.h>
int main()
{
    while(1)
    {
        ;
    }

    return 0;
}

步骤2.打印出提示信息

代码示例:

#include <stdio.h>
int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$\n"); 
    }

    return 0;
}

这样打印出来我们会发现:

所以我们是不能在后面加上 '\n' 的,但是因为有缓冲区的存在,那么应该怎么办呢?

这里需要用到一个函数 --- fflush():刷新缓冲区

代码示例:

#include <stdio.h>
int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
    }

    return 0;
}

步骤3.获取用户的键盘输入[示例 :  输入的是各种指令和选项"ls -a -l" ]

思路:

①需要一个数组来模拟缓冲区 - - - 提取用户输入的字符

②使用fgets读取用户在键盘上的输入,如果读取失败,continue重新进入循环,重新读取,重新打印。

③使用printf测试一下

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
char cmd_line[NUM];

int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
       memset(cmd_line, 0, sizeof cmd_line);
       if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
       {
          continue;
       }
        
       printf("echo:%s\n", cmd_line);

    }

    return 0;
}

但是我们会发现打印出来的结果是:

       这是一个需要注意的小细节的地方,因为在输入的时候,当我们最终输入字符结束的时候,会输入一个“回车”键盘,它会被缓冲区拿到并被识别为“\n”。

示例:输入 ls -l -a    缓冲区读取 ls -l -a \n  

所以这里我们需要把  \n   设置为 \0 作为字符串的结束标志

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
char cmd_line[NUM];

int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
       memset(cmd_line, 0, sizeof cmd_line);
       if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
       {
          continue;
       }
       cmd_line[strlen(cmd_line)-1]  = '\0';    
       //printf("echo:%s\n", cmd_line);    //测试辅助

    }

    return 0;
}

输出结果:

步骤4.命令行字符串解析 [ 示例: " ls -a -l  -s" --->  "ls" "-a" "-l" "-s"]

思路:

①可以把空格定为分割符,然后切割为一个一个的子串

②定义一个指针数组保存切割下来的子串

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];

int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
       memset(cmd_line, 0, sizeof cmd_line);
       if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
       {
          continue;
       }
       cmd_line[strlen(cmd_line)-1]  = '\0';    
       //printf("echo:%s\n", cmd_line);    //测试辅助
       
       g_argv[0] = strtok(cmd_line, SEP);
       int index = 0;
       while(g_argv[index++] = strtok(NULL, SEP));
        
        //测试辅助
       //for(index = 0; g_argv[index]; index++)
            //printf("g_argv[%d]:%s\n", index, g_argv[index]);


    }

    return 0;
}

步骤5.创建子进程进行程序替换

注:进程等待和进程替换后续会更新详细解说

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];

int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
       memset(cmd_line, 0, sizeof cmd_line);
       if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
       {
          continue;
       }
       cmd_line[strlen(cmd_line)-1]  = '\0';    
       //printf("echo:%s\n", cmd_line);    //测试辅助
       
       g_argv[0] = strtok(cmd_line, SEP);
       int index = 0;
       while(g_argv[index++] = strtok(NULL, SEP));
        
        //测试辅助
       //for(index = 0; g_argv[index]; index++)
            //printf("g_argv[%d]:%s\n", index, g_argv[index]);

                                                                                                                       
        pid_t id = fork();
        //child process
        if(id == 0)
        {
           execvp(g_argv[0],g_argv);
           exit(-1);
        }                                                                                               
                                                                                                          
        //father process                                                                                        
        int status = 0;                                                                                 
        pid_t ret = waitpid(id, &status, 0); //阻塞等待                                                 
        if(ret > 0)                                                                                     
        {                                                                                               
           printf("exit code:%d\n",WEXITSTATUS(status));                                               
        }   
        
    }

    return 0;
}

其实写到这里一些简单的命令就已经可以跑了

示例:

退出自己写的shell脚本是 ctrl + c

步骤6.内置命令

但是上述代码有一些小问题,就是我们自己写的shell脚本它并没有让我们的路径发生变化

示例:

        原因是因为,当前我们自己写的shell,无论我们写的任何指令,都是交给了子进程 , 子进程进行进程替换帮助我们来完成的指令,那么指令就只会影响子进程,而不会影响父进程。所以当我们 cd 回到上级目录的时候,父进程根本没有变化,但是可能子进程所在的路径一直在回到上一层路径。

        那么我们想要的是shell脚本所在的路径发生变化,所以我们想要进行判断命令,如果是所谓cd这样的命令,那么我们不能创建子进程,而是直接交给父进程。

  • 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令
  • 内建命令本质就是shell中的一个函数调用
     

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];

int main()
{
    while(1)
    {
       printf("[dwr@VM-1-1-test shell]$");
       fflush(stdout); 
       memset(cmd_line, 0, sizeof cmd_line);
       if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
       {
          continue;
       }
       cmd_line[strlen(cmd_line)-1]  = '\0';    
       //printf("echo:%s\n", cmd_line);    //测试辅助
       
       g_argv[0] = strtok(cmd_line, SEP);
       int index = 0;
       while(g_argv[index++] = strtok(NULL, SEP));
        
        //测试辅助
       //for(index = 0; g_argv[index]; index++)
            //printf("g_argv[%d]:%s\n", index, g_argv[index]);

       if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute;
       {
            if(g_argv[1] != NULL)
            {
              chdir(g_argv[1]); //cd  cd ..
            }
 
           continue;
        
       }
                                                                                                                       
        pid_t id = fork();
        //child process
        if(id == 0)
        {
           execvp(g_argv[0],g_argv);
           exit(-1);
        }                                                                                               
                                                                                                          
        //father process                                                                                        
        int status = 0;                                                                                 
        pid_t ret = waitpid(id, &status, 0); //阻塞等待                                                 
        if(ret > 0)                                                                                     
        {                                                                                               
           printf("exit code:%d\n",WEXITSTATUS(status));                                               
        }   
        
    }

    return 0;
}

 

补充说明:

fflush()

#include<stdio.h>
int main()
{
   int fflush( FILE *stream );
   return 0;
 }

定义:冲洗一个流

头文件:<stdio.h>

注释:如果缓冲区已成功刷新,则Fflush返回0。

 关键字:continue:作用是跳过本次循环continue后面的代码,直接去判断部分,看是否进行下一次循环

memset()

#include<string.h>
int main()
{
   void *memset( void *dest, int c, size_t count );
   return 0;
 }

定义:将缓冲区设置为指定字符 / 可以用来初始化字符串

头文件:<string.h>

注释:memset返回dest的地址

fgets()

#include<stdio.h>
int main()
{
   char *fgets( char *string, int n, FILE *stream );
   return 0;
 }

定义:从流中获取字符串

头文件:<stdio.h>

注释:返回的是string。返回NULL表示错误或文件结束条件

strlen()

#include<string.h>
int main()
{
   size_t strlen( const char *string );
   return 0;
 }

定义:返回的是字符串的长度

头文件:<string.h>

注释:strlen只返回'\0'之前字符串的长度

strtok()

#include<string.h>
int main()
{
   char *strtok( char *strToken, const char *strDelimit );
   return 0;
 }

定义:查找字符串中的下一个标记。(常用于切割字符串)

头文件:<string.h>

注释:

sep参数是个字符串,定义了用作分隔符的字符集合

第一个参数指定一个字符串,它包含了0个或者多个由strDelimit字符串中一个或者多个分隔符分割的标记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。

strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。

strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

如果字符串中不存在更多的标记,则返回 NULL 指针。

chdir()

#include <unistd.h>
int main()
{
   int chdir(const char *path);
   return 0;
 }

定义:更改工作目录

头文件:#include <unistd.h>

返回值:如果成功,则返回0。如果出现错误,则返回-1,并适当地设置errno。


以上就是完整版简单shell脚本的编写,仅供参考

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

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

相关文章

计算机体系结构(1) 介绍和基础

为了跟上我们组学习的进度&#xff0c;打好体系结构的基础&#xff0c;接下来我会持续的学习计算机体系结构的知识。参考的课程是 苏黎世联邦理工 ETH Zurich&#xff1a;Digital Design and Computer Architect Lecture 1: Introduction and Basics_哔哩哔哩_bilibili 这一…

BioXCell,1H6--InVivoMAb anti-canine CD34

1H6单克隆抗体与犬CD34发生反应。CD34是一种I型单体唾液酸粘蛋白样糖蛋白&#xff0c;存在于许多干细胞群体中。CD34由骨髓和外周血中的造血祖细胞以及一些间充质干细胞、基质细胞、胚胎成纤维细胞、肿瘤细胞和成人血管内皮细胞表达。CD34经常被用作量化用于造血干细胞移植后该…

根证书和中间证书安装配置

下载根证书 根证书是建立信任链的基础。一旦客户端安装了根证书&#xff0c;它即可验证由该根证书签发的所有证书。这使得客户端可以信任与该根证书相关的所有服务器和应用程序&#xff0c;从而建立起一个完整的信任链。 如果您的业务用户通过浏览器访问您的Web业务&#xff…

【uniapp】个推H5号码认证一键登录(附代码)

前言 最近在做APP、h5产品&#xff0c;登陆注册成了难题。邮箱验证多数人不会使用&#xff0c;还是短信方便点&#xff0c;短信可以采用号码认证和验证码的方式&#xff0c;前者稍微便宜的&#xff0c;关于性价比和上手程度我推荐个推&#xff0c; 于是有了今天这篇案例记录&a…

低代码如何集成多平台(企业微信、钉钉、飞书)SDK,且听我细细道来

前言 我们是一家做低代码产品开发的公司&#xff0c;我司的低代码产品支持在多个平台环境内使用&#xff1a;有钉钉、企业微信、微信、飞书、Web浏览器&#xff08;如谷歌&#xff09;&#xff0c;后续还会接入更多平台。面对这么多平台&#xff0c;每个平台的SDK可能一致&…

磁盘类型与IOPS性能指标

目录 1.磁盘的访问模式 2.磁盘分类 2.1 HDD机械磁盘 2.1.1 机械磁盘性能 2.1.2 IOPS 2.1.3 提升IOPS性能手段 2.1.4 RAID技术 RAID 0 RAID 1 RAID 5 RAID 6 RAID 10 RAID 50 RAID总结 2.2 SSD固态硬盘 2.2.1 查看磁盘调度算法 2.2.2 修改磁盘调度算法 2.2.3 …

lottery-攻防世界

题目 flag在这里要用钱买&#xff0c;这是个赌博网站。注册个账号&#xff0c;然后输入七位数字&#xff0c;中奖会得到相应奖励。 githacker获取网站源码 &#xff0c;但是找到了flag文件但是没用。 bp 抓包发现api.php&#xff0c;并且出现我们的输入数字。 根据题目给的附…

DNFOMP:杂乱环境中自动驾驶汽车导航的动态神经场最优运动规划器

DNFOMP&#xff1a;杂乱环境中自动驾驶汽车导航的动态神经场最优运动规划器 附赠自动驾驶学习资料和量产经验&#xff1a;链接 摘要 本文介绍了DNFOMP&#xff1a;杂乱环境中自动驾驶汽车导航的动态神经场最优运动规划器。动态变化环境中的运动规划是自动驾驶中最复杂的挑战之…

RA8900CE计时芯片介绍及开发方案

计时芯片 就是一个需要连接32.768k晶振的RTC芯片 规格书阅读 首先我们先读懂这个芯片是怎么用的。 引脚表 封装是这样的&#xff0c;一共10个引脚。 基本上一看这个引脚表就知道大概。 T1和T2是工厂测试的&#xff0c;不用管。 SCL和SDA是IIC通讯用的。 FOUT和FOE就是链…

文献速递:深度学习胰腺癌诊断--胰腺肿瘤的全端到端深度学习诊断

Title 题目 Fully end-to-end deep-learning-based diagnosis of pancreatic tumors 胰腺肿瘤的全端到端深度学习诊断 01 文献速递介绍 胰腺癌是最常见的肿瘤之一&#xff0c;预后不良且通常是致命的。没有肿瘤的患者只需要进一步观察&#xff0c;而胰腺肿瘤的诊断需要紧…

C/C++中局部变量static用法实例

1. 普通局部变量存储于进程栈空间&#xff0c;使用完毕会立即释放&#xff0c;静态局部变量使用static修饰符定义&#xff0c;即使在声明时未赋初值&#xff0c;编译器也会把它初始化为0&#xff0c;并且静态局部变量存储于进程的全局数据区&#xff0c;即使函数返回&#xff0…

用three.js做一个3D汉诺塔游戏(下)

本文由孟智强同学原创。 接上期&#xff1a;《用three.js做一个3D汉诺塔游戏&#xff08;上&#xff09;》 在上一期&#xff0c;我们成功地搭建了基础的 3D 场景。在本期中&#xff0c;我们将对场景进行优化&#xff0c;使其在视觉上更加真实&#xff0c;并为场景中的物体添加…

golang es查询的一些操作,has_child,inner_hit,对索引内父子文档的更新

1.因为业务需要查询父文档以及其下子文档&#xff0c;搞了很久才理清楚。 首先还是Inner_hits,inner_hits只能用在nested,has_child,has_parents查询里面 {"query": {"nested": {"path": "comments","query": {"match…

vulhub之fastjson篇-1.2.27-rce

一、启动环境 虚拟机:kali靶机:192.168.125.130/172.19.0.1(docker地址:172.19.0.2) 虚拟机:kali攻击机:192.168.125.130/172.19.0.1 本地MAC:172.XX.XX.XX 启动 fastjson 反序列化导致任意命令执行漏洞 环境 1.进入 vulhub 的 Fastjson 1.2.47 路径 cd /../../vulhub/fa…

企业IT运维事中故障定位方法及工具

企业IT故障定位指诊断故障直接原因或根因&#xff0c;故障定位有助于故障恢复动作更加有效。故障定位通常是整个故障过程中耗时最长的环节&#xff0c;定位的目标围绕在快速恢复的基础上&#xff0c;而非寻找问题根因&#xff0c;后者由问题管理负责。通常大部分可用性故障&…

GEE案例——小流域汇流计算以缅甸仰光为例(如何绘制除小流域内的河道)

简介 本案例主要介绍如何实现小流域内河流汇流的计算,这里需要用到地形中的坡度以及卷积的计算内容。 流域 流域是指一个地理区域内所有水流都会汇集到同一个主要水体(如河流、湖泊或海洋)的区域。流域由周围山脉、丘陵和地形特征所定义,其中的水流通过降水、融雪和地下…

计算机网络针对交换机的配置

实验 目的 交换机的基本配置&#xff0c;交换机VLAN配置 实验条件 Windows&#xff0c;Cisco packet tracer 实验 内容 交换机的基本配置&#xff0c;交换机VLAN配置 实验 过程 一、交换机的基本配置 进入特权模式 Switch>enable 进入配置模式 Switch#configure ter…

德国SycoTec义齿雕刻机主轴 助力高精密雕铣加工

随着科技的不断进步&#xff0c;义齿加工机械在牙科领域的应用越来越广泛。为了满足市场对高精度、高效率义齿加工设备的需求&#xff0c;SycoTec高速电主轴凭借其卓越的性能和可靠的质量&#xff0c;为CNC四轴、五轴义齿加工设备、五轴联动义齿加工中心、CAM/CAD义齿雕刻机、椅…

算法题中nextInt(),nextLine()的易错陷阱

nextInt()读取空格或回车前的整数 nextLine()读取回车前的一行&#xff0c;空格不会中断读取 陷阱代码如下&#xff1a; public class nextInt {public static void main(String[] args) {Scanner scannernew Scanner(System.in);System.out.println("请输入字符串数组…

C++11可变模板参数:海纳百川的Args

目录 一、可变模板参数的概念及功能 1.1Args的概念与使用 1.2获取args中的参数 二、emplace可变模板参数的实际应用 三、逗号表达式展开参数包 一、可变模板参数的概念及功能 1.1Args的概念与使用 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板…