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


首先附上完整代码

#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 \0cmd_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();//childif(id == 0){execvp(g_argv[0],g_argv);exit(-1);}//fatherint 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 processif(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 processif(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.rhkb.cn/news/301220.html

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

相关文章

【深入理解Java IO流0x05】Java缓冲流:为提高IO效率而生

1. 引言 我们都知道&#xff0c;内存与硬盘的交互是比较耗时的&#xff0c;因此适当得减少IO的操作次数&#xff0c;能提升整体的效率。 Java 的缓冲流是对字节流和字符流的一种封装&#xff08;装饰器模式&#xff0c;关于IO流中的一些设计模式&#xff0c;后续会再出博客来讲…

探索async/await的魔力:简化JavaScript异步编程

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Vulnhub:MHZ_CXF: C1F

目录 信息收集 arp-scan nmap nikto WEB web信息收集 dirmap gobuster ssh登录 提权 获得初始立足点 系统信息收集 横向渗透 提权 信息收集 arp-scan ┌──(root㉿ru)-[~/桌面] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:50:56:…

【Java集合】面试题汇总

Java 集合Java 集合概览1. List, Set, Queue, Map 四者的区别&#xff1f;2. ArrayList 和 Array&#xff08;数组&#xff09;的区别&#xff1f;3. ArrayList 和 Vector 的区别?4. Vector 和 Stack 的区别?&#xff08;了解即可&#xff09;5. ArrayList 可以添加 null 值吗…

paddle实现手写数字模型(一)

参考文档&#xff1a;paddle官网文档调试代码如下&#xff1a; LeNet.py import paddle import paddle.nn.functional as Fclass LeNet(paddle.nn.Layer):def __init__(self):super().__init__()self.conv1 paddle.nn.Conv2D(in_channels1,out_channels6,kernel_size5,stride…

YOLOV9 + 双目测距

YOLOV9 双目测距 1. 环境配置2. 测距流程和原理2.1 测距流程2.2 测距原理 3. 代码部分解析3.1 相机参数stereoconfig.py3.2 测距部分3.3 主代码yolov9-stereo.py 4. 实验结果4.1 测距4.2 视频展示 相关文章 1. YOLOV5 双目测距&#xff08;python&#xff09; 2. YOLOv7双目…

强化学习MPC——(一)

1.什么是强化学习 强化学习是机器学习的一种&#xff0c;是一种介于监督学习和非监督学习的机器学习方法。 学习二字就很形象的说明了这是一种利用数据&#xff08;任何形式的&#xff09;来实现一些已有问题的方法&#xff0c;学习方法&#xff0c;大致可以分为机器学习&…

说说TCP为什么需要三次握手和四次挥手?

一、三次握手 三次握手&#xff08;Three-way Handshake&#xff09;其实就是指建立一个TCP连接时&#xff0c;需要客户端和服务器总共发送3个包 主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备 过程如下&#xff…

Redis 常见面试题

目录 1. Redis是什么&#xff1f;2. Redis优缺点&#xff1f;3. Redis为什么这么快&#xff1f;4. 既然Redis那么快&#xff0c;为什么不用它做主数据库&#xff0c;只用它做缓存&#xff1f;5. Redis的线程模型&#xff1f;6. Redis 采用单线程为什么还这么快&#xff1f;7. R…

如何使用生成式人工智能撰写关于新产品发布的文章?

利用生成式人工智能撰写新产品发布文章确实是一种既有创意又高效的内容生成方式。以下是如何做到这一点的指南&#xff0c;附带一些背景信息&#xff1a; • 背景&#xff1a;在撰写文章之前&#xff0c;收集有关您的新产品的信息。这包括产品的名称、类别、特点、优势、目标受…

解决Xshell连接不上虚拟机

相信有很多同学和我一样遇到这个问题&#xff0c;在网上看了很多教程基本上都先让在虚拟机输入ifconfig命令查看ip地址&#xff0c;弄来弄去最后还是解决不了&#x1f62d;&#x1f62d;&#xff0c;其实问题根本就不在命令上&#xff0c;很大概率是我们的虚拟机没有开启网卡&a…

基于单片机便携式测振仪的研制系统设计

**单片机设计介绍&#xff0c;基于单片机便携式测振仪的研制系统设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机便携式测振仪的研制系统设计概要主要涉及利用单片机作为核心控制器件&#xff0c;结合测振原理和技术&#x…

python-可视化篇-turtle-画爱心

文章目录 原效果替换关键字5为8&#xff0c;看看效果改下颜色 原效果 import turtle as tt.color(red,pink) t.begin_fill() t.width(5) t.left(135) t.fd(100) t.right(180) t.circle(50,-180) t.left(90) t.circle(50,-180) t.right(180) t.fd(100) t.pu() t.goto(50,-30) t…

蓝鲸6.1 CMDB 事件推送的开源替代方案

本文来自腾讯蓝鲸智云社区用户&#xff1a;木讷大叔爱运维 背景 在蓝鲸社区“社区问答”帖子中发现这么一个需求&#xff1a; 究其原因&#xff0c;我在《不是CMDB筑高墙&#xff0c;运维需要一定的开发能力&#xff01;》一文中已经介绍&#xff0c;在此我再简单重复下&#…

JavaScript实现全选、反选功能(Vue全选、反选,js原生全选、反选)

简介&#xff1a; 在JavaScript中&#xff0c;实现全选和反选通常是通过操作DOM元素和事件监听来实现&#xff1b; 全选功能&#xff1a;当用户点击一个“全选”复选框时&#xff0c;页面中所有具有相同类名的复选框都将被选中&#xff1b; 反选功能&#xff1a;用户点击一个…

ARP寻址过程

当知道目标的IP但是不知道目标的Mac地址的时候就需要借助ARP寻址获取目标的Mac地址&#xff0c;传输层借助四元组&#xff08;源IP源端口&#xff1a;目标IP目标端口&#xff09;匹配&#xff0c;网络层借助IP匹配&#xff0c;数据链路层则根据Mac地址匹配&#xff0c;数据传输…

局域网共享文件夹怎么加密?局域网共享文件夹加密方法介绍

在企业局域网中&#xff0c;共享文件夹扮演着重要的角色。为了保护数据安全&#xff0c;我们需要加密保护局域网共享文件夹。那么&#xff0c;局域网共享文件夹怎么加密&#xff1f;下面我们来了解一下吧。 局域网共享文件夹加密方法 局域网共享文件夹加密推荐使用共享文件夹加…

在git上先新建仓库-把本地文件提交远程

一.在git新建远程项目库 1.选择新建仓库 以下以gitee为例 2.输入仓库名称&#xff0c;点击创建 这个可以选择仓库私有化还公开权限 3.获取仓库clone链接 这里选择https模式就行&#xff0c;就不需要配置对电脑进行sshkey配置了。只是需要每次提交输入账号密码 二、远…

万字源码解析!彻底搞懂 HashMap【一】:概念辨析与构造方法源码解析

HashMap 的底层原理和扩容机制一直都是面试的时候经常被问到的问题&#xff0c;同时也是集合源码中最难阅读的一部分&#x1f622;&#xff0c;之前更新的 ArrayList 源码阅读收获了很多朋友的喜欢&#xff0c;也给了我很多自信&#xff1b;本次我准备完成一个关于 HashMap 源码…

python练习三

模式A num int(input("请输入模式A的层数&#xff1a;")) for i in range(1, num 1):# 画数字for j in range(1, i 1):print(str(j) "\t", end"")print() 模式B num int(input("请输入模式B的层数&#xff1a;")) for i in ran…