【Linux进程】Linux Shell编程实战:构建简易脚本示例与技巧详解

news2024/11/13 10:05:39

📝个人主页🌹:Eternity._
⏩收录专栏⏪:Linux “ 登神长阶 ”
🤡往期回顾🤡:暂无
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀Linux进程

  • 📒1. 获取输入
    • 🌞获取环境变量
    • ⭐获取用户输入
  • 📚2. 分割字符串
  • 📜3. 检查内建命令
  • 📝4. 执行程序
  • 📖5. 总结


前言:在Linux的浩瀚宇宙中,Shell脚本无疑是连接用户与系统之间的桥梁,它赋予了用户强大的自动化处理能力,使得繁琐的重复性任务变得轻松高效。对于每一位Linux爱好者、系统管理员或是开发人员而言,掌握Shell脚本编写技能无疑是一项不可或缺的宝贵财富

本篇文章旨在通过一系列精心设计的简易Shell脚本示例,引领读者踏入Shell编程的奇妙世界。我们不会深入复杂的语法细节和高级特性,而是聚焦于最基础、最实用的知识点,让初学者也能快速上手,体验到编写Shell脚本的乐趣与便利

让我们一起,在Shell脚本的海洋中扬帆起航,探索更多未知的精彩吧!


📒1. 获取输入

在构建简易Shell的时候我们首先就是要获取输入

  • 获取环境变量:能够像shell一样运行会出现部分环境变量
  • 获取用户输入:获取用户输入的指令

🌞获取环境变量

在运行shell时就会出现一些环境变量,我们自定义构建的shell中,也可以实现这一步

在这里插入图片描述
用户名:pxt
主机名:hecs - 198213
当前目录:myshell

// 获取环境变量 user,hostname,pwd,home
char *homepath()
{
    char *home= getenv("HOME");
    if(home) return home;
    else return (char*)".";
}
 
const char *getUsername()
{
   	const char *name = getenv("USER");
    if(name) return name;
    else return "none";                                                                                                                                                                                    
}

const char *getHostname()
{

    const char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}

const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}

printf("[%s@%s %s]$ ",getUsername(), getHostname(), getCwd());

在这里插入图片描述
这里我们直接将绝对路径展示了出来,当然没什么影响

这里我们用到了一个函数getenv(),这个函数用于获取环境变量的值,它的头文件是<stdlib.h>,在shell脚本中,获取环境变量的值是通过直接使用变量名来实现的,而不需要特别的函数或方法

在这里插入图片描述
在我们完成最基础的一步之后,我们要开始模拟我们使用的shell的使用方式,接下来一步就是获取用户输入


⭐获取用户输入

获取用户输入时,我们可以创建一个字符数组用来存储用户的输入

#define NUM 1024

char usercommand[NUM];

在获取用户输入时,输完一个指令后不换行,进行下一次输入。
但是在我们输入完成时,总会回车确认,因此我们需要修改最后一次输入

getUserCommand:

// 对用户输入进行封装
int getUserCommand(char *command, int num)
{
    printf("[%s@%s %s]$ ",getUsername(), getHostname(), getCwd());
    
    char *r = fgets(command, num, stdin); // 最后还是会输入'\n',回车
    if(r == NULL) return -1; // TODO
    // 将最后一次输入修改成'\0'
    command[strlen(command)-1] = '\0'; // 这里不会越界
    return strlen(command);                                                                                                                                                                                
}

📚2. 分割字符串

在Shell中,分割字符串是一个常见的操作,它涉及到将一串包含多个子字符串(可能由空格、逗号、冒号等分隔符分隔)的文本分割成单独的部分,以便进行进一步的处理或赋值给不同的变量

在我们完成用户输入指令的读取之后,我们需要将字符串进行分割,让我们的指令能够被正确的读取并且实现出来,通常我们的分隔符是' '(空格)

#define SIZE 64	// 设置argv的大小
#define SEP " " // 分隔符 " "
#define debug 1 // 用来测试是否能够成功分割字符串

char *argv[SIZE]; // 用来储存分割后的字符串

而关于分割字符串,我们在C语言的学习中可能提到过一个字符串函数strtok(),它的头文件是<string.h>,用于分割字符串。它基于一系列的分隔符来分割字符串,并返回指向被分割出的字符串的指针

char *strtok(char *str, const char *delim);

在这里插入图片描述
commandSplit:

void commandSplit(char *in, char *out[]) //  in -> usercommand
{										 //  out -> argv	
   int argc = 0;
   out[argc++] = strtok(in, SEP);
   while(out[argc++] = strtok(NULL, SEP)); // 我们只需要将用户输入全部读取即可

// 用来测试是否能够成功分割字符串
#ifdef debug
   int i = 0;
   for(i = 0; out[i]; i++)
   {
       printf("%d:%s\n", i, out[i]);
   } 
#endif
}

在完成分割字符串之后,我们就可以用很久之前学习的进程的知识,让它运行起来的,但是仅仅如此我们的很多命令是根本无法实现的,所以我们还需要进行一步,检查内建命令!


📜3. 检查内建命令

内建命令(也称为内置命令)是由Shell(如Bash)自身提供的命令,而不是文件系统中的某个可执行文件。这些命令通常比外部命令执行得更快,因为它们不需要通过磁盘I/O来加载外部程序,也不需要创建新的进程

我们简单实现3个内建命令:cd,exprot,echo

doBuildin:

char cwd[1024]; // 存储
char enval[1024]; // for test

int lastcode = 0; // 存储上一条指令的返回信息

void cd(const char *pash)                                                                                                                                                                                  
{
    chdir(pash);
    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp); // 改变当前的路径
    putenv(cwd); // 改变环境变量
}

int doBuildin(char *argv[])
{
    if(strcmp(argv[0], "cd") == 0)
    {
        char *pash = NULL;
        if(argv[1] == NULL) pash = homepath(); // 当我们cd之后不更任何输入时,
        									   //我们直接返回家目录
        else
        {
            pash = argv[1];
        }
        cd(pash);
        return 1;
    }
	else if(strcmp(argv[0], "exprot") == 0)
    {
        if(argv[1] == NULL) return 1;
                                                                                                                                                                                                           
        strcpy(enval, argv[1]);
        putenv(enval); // 用于增加环境变量内容
        return 1;
    }
    
	else if(strcmp(argv[0], "echo") == 0)
	{
		if(argv[1] == NULL)
   		{
       		printf("\n");
        	return 1;
    	}
       
		if(*(argv[1]) == '$' && strlen(argv[1]) > 1)
    	{
        	char *val = argv[1]+1; // argv[1]为'$',argv[1]+1则为'$'后的字符
        	if(strcmp(val, "?") == 0)
        	{
        		// lastcode 存储上一条指令的返回信息,初始状态为0
        		// 当程序执行时,出错时会改变lastcode的值,打印后,重新赋值为0
        		printf("%d\n", lastcode);
            	lastcode = 0;
        	}
        	else // '$'后不跟"?",则获取环境变量
        	{
            	const char *enval = getenv(val);
            	if(enval) printf("%s\n", enval);
            	else printf("\n");
        	}
        	return 1;
    	}
		else // 直接输出字符
		{
        	printf("%s\n",argv[1]);
        	return 1;
    	}
    }
    return 0;
}

在内建命令的检查中,我们又会用到一些函数:

  • getcwd:用于获取当前工作目录的绝对路径
  • chdir:用于改变当前工作目录
    chdir命令通常通过cd命令来实现,因为cd是chdir的别名,两者具有相同的功能。不过,在编程语言中(如C、PHP等),chdir则是一个具体的函数,用于在程序中动态改变当前工作目录
  • sprintf:用于将格式化的数据写入字符数组中
  • putenv:用于改变或增加环境变量内容的函数

📝4. 执行程序

在完成前面的铺垫之后,我们就可以创建进程来实现我们的程序了,在之前学习进程时,我们需要用到3个头文件

#include<unistd.h>
#include<sys/types.h>
#include<wait.h>

execute:

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0)
    {
        // 子进程
        execvp(argv[0], argv);
        exit(1);
    }
    else{
        // 父进程
        int status = 0;

        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
        	// 将现在状态提供个lastcode
            lastcode = WEXITSTATUS(status);
        }
    }
    return 0;                                                                                                                                                                                              
}

在这里插入图片描述

execvp是C语言(特别是在Unix和类Unix系统,如Linux)中用于执行外部程序的一个系统调用函数。这个函数通过查找环境变量(特别是PATH环境变量)来定位并执行指定的文件,同时将参数列表传递给该程序

在这里插入图片描述

以上就是对一些基本操作的封装,让我们看一下主函数main

main:

int main()
{
	// shell是一个一直循环的程序
    while(1)
    {
        char usercommand[NUM];
        char *argv[SIZE];
        
        // 获取输入
        int n = getUserCommand(usercommand, sizeof(usercommand));
        // 当获取输入时,返回一个小于0的数时,我们直接continue返回,不用往下继续走了
        if(n <= 0) continue;
        // 分割字符串 (strtok)
        commandSplit(usercommand, argv);
        // check Buildin
        n = doBuildin(argv);
        // 一般内建命令不会出错
        if(n) continue;
        // 执行命令
        execute(argv);
                                                                                                                                                                                                           
    }   
    return 0;
}

在这里插入图片描述

注意:当我们输入指令输出字符时,Backspace键是不能直接删除的,我们需要Ctrl + Backspace 组合键用于删除光标前的一个单词


我们来思考函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间

在这里插入图片描述

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


📖5. 总结

在探索和学习编写Linux中简易shell脚本的旅程即将告一段落之际,我们不禁回望这段充满挑战与收获的时光。Shell脚本,作为Linux系统中不可或缺的一部分,以其强大的自动化能力和灵活的语法结构,成为了系统管理员、开发者以及任何希望提高工作效率的用户的得力助手

通过本文的引导,我们一同踏入了shell脚本编程的大门,从基础的语法规则到实际应用中的小技巧,每一步都见证了知识的积累与能力的提升。我们学会了如何创建和运行脚本,掌握了变量、条件判断、循环控制等核心概念,更通过实例演练,亲身体验了脚本在文件处理、自动化任务等方面的广泛应用

最后,让我们以一颗永不停歇的求知之心,继续前行在Linux与shell脚本编程的广阔天地中,共同书写属于我们的精彩篇章!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

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

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

相关文章

《Nginx核心技术》第17章:使用自签CA配置HTTPS加密反向代理访问

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 星球项目地址&#xff1a;https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀&#xff0c…

事务方法中存在远程调用

1. 背景 在实现下单的方法中&#xff0c;首先通过远程调用查询了地址簿信息和服务信息等&#xff0c;然后将这些信息和前端传入的预约时间、服务项等信息封装到order实体类&#xff0c;然后调用Mybatis-Plus提供的ServiceImpl类的save方法&#xff0c;向数据库表中插入数据。 …

基于51单片机的220V交流数字电流表proteus仿真

地址&#xff1a;https://pan.baidu.com/s/1QmpPLvDTuW7QG7P-JCLPPg 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectron…

直流无刷电机霍尔线序自学习解释

直流无刷电机霍尔线序自学习 步骤详解 1. 初始连接 连接电机的三相线&#xff1a;A、B、C。连接霍尔传感器线&#xff1a;HA、HB、HC。 2. 输入电压组合与霍尔信号记录 电机的电压输入组合和霍尔信号记录是电机控制系统中至关重要的一部分&#xff0c;它们决定了电机的运转…

CODESYS标准化编程之输入输出映射

在介绍输入输出映射之前大家需要了解开关量防抖滤波功能块,相关链接如下: 开关量防抖滤波器(梯形图和SCL源代码)_开关量输入滤波程序-CSDN博客文章浏览阅读724次。本文介绍了开关量防抖滤波器的原理和应用,包括梯形图和SCL源代码的实现。通过防抖滤波功能块,可以有效滤除低…

2024年9月15日

30min 1.配置数据库得到数据表 2.连接数据库 英语六级 遇到了报错

YOLOv10改进系列,YOLOv10颈部网络SPPF替换为FocalModulation

摘要 焦点调制网络(简称FocalNets),其中自注意力(SA)完全由焦点调制模块取代,用于在视觉中建模标记交互。焦点调制包括三个组件:(i)焦点情境化,通过一堆深度卷积层实现,从短到长范围编码视觉上下文,(ii)门控聚合,选择性地将上下文聚集到每个查询标记的调制器中…

OpenCV和Tesseract OCR识别复杂验证码喽~~

目录 代码实现思路 流程&#xff1a; 主要流程&#xff1a; 整体代码 效果展示 原图 处理之后的图 总结 流程图 代码实现思路 使用 OpenCV 进行图像预处理&#xff0c;并通过 Tesseract OCR 来识别验证码中的字符。以下是其实现思路的详细讲解&#xff1a; 流程&…

人工免疫算法(AIS算法)求解实例---旅行商问题 (TSP)

目录 一、采用AIS求解 TSP二、 旅行商问题2.1 实际例子&#xff1a;求解 6 个城市的 TSP2.2 **求解该问题的代码**2.3 代码运行过程截屏2.4 代码运行结果截屏&#xff08;后续和其他算法进行对比&#xff09; 三、 如何修改代码&#xff1f;3.1 减少城市坐标&#xff0c;如下&a…

【AIGC cosplay】让大模型扮演求职者,我当hr来面试

【AIGC cosplay】让大模型扮演求职者&#xff0c;我当hr来面试 背景前摇&#xff08;省流可不看&#xff09;&#xff1a; 工位附近有几位人力资源的老师&#xff0c;我发现他们这个职位可以看到好多别人的简历&#xff0c;还能跟好多人聊天&#xff0c;完事了跟同事悄悄吐槽…

<<编码>> 第 12 章 二进制加法器--8位加法器 示例电路

8 位加法器内部结构 info::操作说明 鼠标单击逻辑输入切换 0|1 状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/code-hlchs-examples/assets/circuit/code-hlchs-ch12-08-8-bit-adder-internal.txt 8 位加法器 info::操作说…

操作系统——OS概述

操作系统——OS概述 操作系统概念 操作系统&#xff08;Operation System&#xff0c;OS&#xff09;是管理计算机硬件与软件资源的计算机程序。由此来看&#xff0c;OS是一种特殊的软件&#xff0c;它给用户和其他软件提供接口和环境&#xff0c;是计算机系统中最基本的系统…

20、Cereal

难度 高 目标 root 权限 2 Flag 使用VMware进行运行 信息收集 kali 192.168.152.56 靶机 192.168.152.65 第一次遇到这么快的端口扫描 可以看到存在ftp anoymous访问 同时还有大量的tcpwrapped状态&#xff0c;这个状态我还没见过 另外有个数据库和两个我网站服务&#xf…

4.TensorBoard的使用(一)--add_scalar()

Tensorboard的使用&#xff08;一&#xff09; TensorBoard 是一个可视化工具&#xff0c;用于监控和调试深度学习模型的训练过程。 1.使用add_scalar()进行简单绘图 首先需要导入Tensorboard包 from torch.utils.tensorboard import SummaryWriter创建一个SummaryWriter类的…

深入理解SpringBoot(一)----SpringBoot的启动流程分析

1、SpringApplication 对象实例化 SpringApplication 文件 public static ConfigurableApplicationContext run(Object[] sources, String[] args) {// 传递的source其实就是类Bootstrapreturn new SpringApplication(sources).run(args);// 实例化一个SpringApplication对象执…

CSS—4

1.定位 1.相对定位 2.绝对定位 3.固定定位 4.粘性定位 5.定位的特殊应用 2.布局-版心 3.布局-常用布局名词 4.布局-重置默认样式

【云原生监控】Prometheus监控系统

Prometheus监控系统 文章目录 Prometheus监控系统资源列表基础环境一、部署Prometheus服务1.1、解压1.2、配置systemctl启动1.3、监听端口1.4、访问Prometheus仪表盘 二、部署Node-Exporter2.1、解压2.2、配置systemctl启动2.3、监听端口2.4、访问node-exporter仪表盘 三、配置…

Java接口的艺术:探索接口特性与面向接口编程

在Java的世界里&#xff0c;接口&#xff08;Interface&#xff09;是一种强大的抽象机制&#xff0c;它定义了一组方法规范&#xff0c;但不实现这些方法。接口在Java编程中扮演着至关重要的角色&#xff0c;特别是在实现多态和面向接口编程&#xff08;Interface-Oriented Pr…

Linux进程状态进程优先级

目录 一、操作系统的进程状态 1.1运行状态 1.2阻塞状态 1.3挂起 二、Linux下具体的状态 三、进程的优先级 3.1基本概念 3.2查看进程优先级的命令 3.3修改进程优先级的命令 3.4其他概念 3.5并发 一、操作系统的进程状态 1.1运行状态 当一个进程准备就绪&#xff0c…

MouseArea元素

常用信号 onClicked&#xff0c;鼠标点击onPressed&#xff0c;鼠标按下onReleased&#xff0c;鼠标释放 import QtQuickWindow {width: 640height: 480visible: truetitle: qsTr("Hello World")Rectangle{id:rectwidth: 100height: 100color:"red"MouseA…