当前位置: 首页 > news >正文

自行编写一个简单的shell!

本文旨在编写一个简单的shell外壳程序!功能类似于shell的一些基本操作!虽然不能全部实现shell的一些功能!但是通过此文章,自己写一个简单的shell程序也是不成问题!并且通过此文章,可以让读者对linux中一些环境变量等基本概念有更深的理解!希望读完本篇文章能对读者有一定的收获!文末会附带自己编写shell的源码!


好的废话少说,正文开始!

首先我们先来看一下linux中的shell长什么样子!

这是其shell刚启动的时候的样子!其外貌就是一个中括号内部加上一系列的东西!其实当我们认真观察,不难发现,里面包括的就是“用户名“+“@”+“主机名字”+“当前工作路径!”那么发现了此规律之后我们不难实现此描述框!那么接下来我们就着手与这些描述框的实现!

linux描述框的实现!

其中要想获得我们的用户名!我们其实可以通过环境变量进行获取,那么该如何获取环境变量的值呢?这里就不得不引进一个获得环境变量的值的函数了!

getenv(“USER”)

通过查询man手册,我们可以发现getenv()函数只需要传递一个参数即可!那么此参数是什么呢?其实此参数就是我们想要获得环境变量的值的名字!所以要想获得用户名,我们可以直接使用getenv(USER),即可获得我们想要的用户名!我们可以验证一下USER对应的环境变量是否真的是我们所要的环境变量名!我们可以通过echo $USER  此命令来判断是否真的是我们想要的用户名!

不难看出,USER对应的环境变量确实是我们的用户名!


getenv(“HOSTNAME”)

既然有了用户名,那么我们的主机名如何获得呢?思路还是调用getenv(HOSTNAME)操作!获取主机名!同样的也可以通过echo命令进行验证!这里就不再累赘了!


getenv(“PWD”)

最后再来获取我们的当前工作目录!也是调用getenv函数!同样的可以通过echo命令进行验证!


那么这些基本的环境变量都出来了,我们是否可以通过上述思路来创建一个简单的描述框呢?

代码如下:

其中这里为了方便起见,直接将各个函数进行封装!保证代码的健壮性!

其中还需要扩充的几点有:

为了区别与系统的shell,我们在描述框后面加上一个#以区分系统的$ !这样我们的描述框已经基本实现了!

获取用户指令以及将其分割!

那么基本的描述框已经实现了!我们还需要做的一点就是获取用户输入的指令!那么如何获取用户的指令呢?思路很简单:定义一个数组,然后将用户输入的字符放到数组中即可!!那么能否用scanf函数呢?答案是肯定不行!因为用户输入的指令一般都是指令+选项!其中指令和选项之间都是有着空格来间隔区分的!那么应该如何获取用户的输入呢?答案很简单,用fgets函数即可,那么接下来我们就来介绍一下fgets函数的用法!!

fgets函数

 通过查询man手册可以看出,其中fgets函数中有三个参数,第一个是就是缓冲区即(将要被写到哪里的地址!)第二个参数表示此缓冲区的大小!第三个参数是用哪些流进行写入!一般第三个参数我们都选择(stdin标准输入流)进行写入!

既然介绍了fgets函数的用法,那么我们就知道我们需要创建一个数组来存放即将要写入的数据!数组的大小自己来定义即可!

那么用户的指令获取成功之后,我们需要将用户的指令进行打散然后利用execvp进行替换即可!那么如何进行打散这段字符串呢?这里就不得不引进我们C语言中的strtok函数了!

strtok()函数!

查询man手册可以得知,strtok有两个参数!其中第一个参数是将要打散的原字符串,第二个字符串指的是用于打散的标记符都有哪些。

返回值:第一次调用,返回标记符第一次出现的位置,然后并将标记符转化为\0,此时会记住此位置!然后再次使用的使用第一个参数只需要传NULL指针即可!如果最终不可再进行分割的时候,返回值就会返回NULL!这样就可以将原字符串进行打散!我们的目的是想要将其打散放在一个数组中,方便之后使用!所以我们还需要自己再定义一个指针数组用于存放分割后的各个字符串!

通过以上的思路,我们就可以将用户命令和将命令打散此功能进行实现了!

代码如下:

其中第60行是将最后的\n转化为\0,防止其进行跳行!!其中在commandSplit函数中,我们还设置了条件宏!用于检查我们的代码是否将原字符串进行正确的打断!如果最后不想要打印出分割后的字符串,可以将宏定义取消即可!其中char*out[]表示打散后的数组!!char *in 表示的是原字符串!spint是一个宏定义用来标明分割字符串都有哪些,这里分割符只要空格!

至此,描述框和获取用户指令都已经实现了!

完成进程替换!

那么我们如何将用户的指令转化为shell的操作呢?这里就得引进进程替换的概念!我们需要将进程进行替换来让他执行我们想让他执行的代码!

那么进程替换有很多中调用方式?我们应该选择那种呢?其实很简单!我们已经将用户的命令行进行打断分散处理了,所以我们完全可以根据v的特性来进行选择,又因为我们并不知道用户以后需要输入的指令,所以我们也不知道其指令所在路径,所以我们就可以使用execvp这个系统调用来进行进程替换!其中v我们已经有了!p默认为我们提供了路径,所以用户的指令肯定是存放在v[0]上的!所以我们的进程替换就可以写出来了!

需要注意的是,我们要进行进程替换的时候,一定不要让我们的父进程进行替换!因为一旦父进程进行替换的时候,如果进程挂掉了,那么我们的shell不就是结束了么,所以我们可以使用fork来创建子进程来进行进程替换,而父进程只需要等待子进程退出,回收其资源即可!

下面来看一下进程替换的代码!

至此,进程替换的指令也可以实现了,我们自定义的shell程序也能实现ls  top pwd 等操作了!但是对于其他命令我们自定义的shell程序却不能正确的执行了!例如cd命令,还有export命令!这是为什么呢?这就不得不引进内建命令了!

内建命令

何为内建命令呢?内建命令就是这些命令只能由bash自己执行!而不能让子进程进行执行!那么我们常见的linux中有哪些命令是内建命令呢,下面就来简单的介绍几个内建命令,并且在我们自定义的shell中实现这些内建命令!!

cd命令!!

其中cd是一种常见的内建命令!这个指令只能交付给父进程自己执行,而不能交付给子进程让子进程执行!因为cd指的就是改变当前的路径,如果交给子进程进行执行,那么父进程的路径将不会修改!那么该如何进行编写我们shell中的cd命令呢?

代码如下:

其中cd主要进行的操作就是将当前的工作目录进行修改!那么如何修改当前的工作目录呢?这里就不得不引进chdir这个系统调用了!

chdir()

其中chdir函数只有一个参数,这个参数代表的是将要修改的路径!我们只需要定义一个字符数组,然后将我们要修改的路径存放到此数组中,然后将此数组就进行传递即可完成改变当前的路径!其中还需要将当前的环境变量PWD也进行修改!创建一个临时数组和全局数组,全局用于存放环境变量的值!然后将修改后的环境变量的值写入到全局数组中!最后再将环境变量进行同步!只需要调用putenv就可以将环境变量进行修改!

export命令!

还有一个常见的内建命令就是export,那么什么是export呢?export命令就是将我们定义的变量导入到环境变量之中!下面来看一下如何实现我们自己shell的export命令!

代码如下:

其中我们需要定义一个全局变量的数组用于存放我们的环境变量的值!如果我们使用的是局部变量的话!就会导致每次用户输入命令的时候,我们不更新环境变量,其环境变量就会自动消失!这是因为局部变量的局部性!所以定义一个全局变量是最为合适的!但是此代码也有一个小bug,就是当再导入一个新的环境变量的时候,之前的那个环境变量就会消失!

既然环境变量也能导出了,那么我们总得知道是否真正的将其导出了,这里就得引出了echo命令了,因为此命令也是内建命令,所以也得交给我们的父进程自己执行!下面就来写一下关于echo命令的代码!

ehco命令!

其中echo命令简单分为三个功能!第一个是回显出退出码!第二个是显示出环境变量的值!第三个就是普通的回显字符串!

对于第一个回显错误码:我们只需要判断其分割后的第二个字符串是否是“$”即可!然后根据$后面跟的字符即可判断出来,如果$后面跟的是"?"字符的话,那就是显示出退出码的信息!如果“$”后面跟的不是"?"而是一个字符串!那么就是显示出其环境变量的值!最后如果连"$"字符都没有的话,那就是简单的回显字符串了!

这就是echo命令实现的简单逻辑了!

但是我们写的shell还有大多数功能没有实现,比如本地变量的存储,以及重定向的操作!对于本地变量的存储,我们可以用malloc在堆内申请空间来存储变量中的值,对于重定向!我们可以利用dup函数进行重定向的操作!下面来看一下简单的检查是否有重定向的函数吧!

重定向:

其中SkipSpace是一个宏,其作用就是跳过空格的!我们检查是否有重定向,顺便也能将文件名与命令相分割,然后在execu函数体内进行重定向的操作!

这样我们的shell也能支持重定向的功能了!

至此我们自己的shell已经初步完成了,它能完成一些简单的操作!!希望读完本文,读者也尝试写一下shell的实现!

下面将源码附在下面!

源码

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
#define Size 50
#define NUM 1024
#define spint " "
//#define debug 1#define NOredir 0
#define AppendRedir 3
#define InputRedir  1
#define OutputRedir 2#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; }while(0)char *filename=NULL;
int redir=NOredir;
int lastcode=0;
char enval[1024];
char cwd[1024];
// char eni[1024];
const char* getUser()
{char* user=getenv("USER");if(user){return user;}else{return "none";}}const char*getHost()
{char *host=getenv("HOSTNAME");if(host){return host;}else{return "none";}
}char*gethome()
{char *pwd=getenv("PWD");if(pwd){return pwd;}else{return "none";}
}int getcommand(char*command,int n)
{printf("[%s@%s %s]#",getUser(),getHost(),gethome());char*r=fgets(command,n,stdin);if(r==NULL) return 0 ;command[strlen(command)-1]='\0';return 1;
}void commandSplit(char *in,char *out[])
{int argc=0;out[argc++] =strtok(in,spint);while(out[argc++]=strtok(NULL,spint));
#ifdef debug int i=0;for(i=0;out[i];i++){// printf("%d:%s\n",i,out[i]);printf("%s\n",out[i]);}// printf("\n");
#endif
}//只需要将用户的命令行数组指令传递过来即可!
int execute(char* argv[])
{pid_t rit=fork();if(rit==0){int fd=0;if(redir==InputRedir){fd = open(filename, O_RDONLY); // 差错处理我们不做了dup2(fd, 0);}else if(redir==OutputRedir){fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);}else if(redir==AppendRedir){fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{//do nothing}//子进程!用于进程切换!而不是让父进程bash直接自己运行!//其中进程替换直接用execvp函数即可,因为我们有了用户的命令行了!execvp(argv[0],argv);exit(0);//如果替换失败就会退出!负责代表进程替换成功!}else{int status=0;//父进程!只需要等待子进程退出即可!pid_t ret=waitpid(rit,&status,0);if(ret==rit){// printf("wait success\n");lastcode = WEXITSTATUS(status);// printf("%d",lastcode);//  return 0;}}return 0;
}void cd(const char*path)
{chdir(path);char tem[1024];getcwd(tem,sizeof(tem));sprintf(cwd,"PWD=%s",tem);putenv(cwd);}
//检查是否为内建命令并执行!
int dobuildin(char*argv[])
{//cd命令!if(strcmp(argv[0],"cd")==0){char *path=NULL;if(argv[1]==NULL)  {path=gethome(); }else path=argv[1];cd(path);return 1;}else if(strcmp(argv[0],"export")==0){ if(argv[1]==NULL) return 1; strcpy(enval,argv[1]);//  strcpy(envir,argv[1]);// putenv(envir);//此处需要用全局变量数组来存储env 因为一旦使用局部变量的时候,会随着用户输入的指令putenv(enval);//此处需要用全局变量数组来存储env 因为一旦使用局部变量的时候,会随着用户输入的指令//环境变量会消失!return 1;}else if(strcmp(argv[0],"echo")==0){//与系统中的echo保持一致!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{char *enval=getenv(val);if(enval) printf("%s\n",enval);else{printf("\n");}// return 1;}return 1;}else{printf("%s\n",argv[1]);return 1;}// return 1;}else if(0){}return 0;
}void checkRedir(char usercommand[], int len)
{// ls -a -l > log.txt// ls -a -l >> log.txtchar *end = usercommand + len - 1;char *start = usercommand;while(end>start){if((*end) == '>'){if(*(end-1) == '>'){*(end-1) = '\0';filename = end+1;SkipSpace(filename);redir = AppendRedir;break;}else{*end = '\0';filename = end+1;SkipSpace(filename);redir = OutputRedir;break;}}else if(*end == '<'){*end = '\0';filename = end+1;SkipSpace(filename); // 如果有空格,就跳过redir = InputRedir;break;}else{end--;}}
}int main()
{while(1){char userCommand[NUM];char* argv[Size];//显示框架!获取用户输入的指令!int n= getcommand(userCommand,sizeof(userCommand));// if(n==0) continue;// printf("%s")//将用户的命令进行切割!checkRedir(userCommand,strlen(userCommand));commandSplit(userCommand,argv);//判断命令是否为内建命令1!int k=dobuildin(argv);if(k) continue;//创建子进程用于进行进程替换!execute(argv);}// return 0;
}

相关文章:

自行编写一个简单的shell!

本文旨在编写一个简单的shell外壳程序&#xff01;功能类似于shell的一些基本操作&#xff01;虽然不能全部实现shell的一些功能&#xff01;但是通过此文章&#xff0c;自己写一个简单的shell程序也是不成问题&#xff01;并且通过此文章&#xff0c;可以让读者对linux中一些环…...

mvn site 命令

概述 在Maven中&#xff0c;site指的是一个特定的阶段&#xff0c;其目的是生成项目相关的站点文档。这些站点文档可以为项目的开发者、用户、以及其他利益相关者提供有关项目的详细信息。 Maven的站点文档通常包括以下内容&#xff1a; 项目信息&#xff1a;这部分提供了关于…...

<JavaEE> 经典设计模式之 -- 定时器

目录 一、定时器的概念 二、Java 标准库中的定时器 三、实现自己的定时器 一、定时器的概念 什么是定时器&#xff1f;定时器是软件开发中的一个常用且重要组件&#xff0c;作用是在达到设定时间后&#xff0c;执行指定的代码。 二、Java 标准库中的定时器 1&#xff09;T…...

【C++ Primer Plus学习记录】if语句

目录 一、if语句 二、if else语句 三、格式化if else语句 四、if else if else结构 一、if语句 if语句让程序能够决定是否应执行特定的语句。 if有两种格式&#xff1a;if和if else。 if语句的语法与while相似&#xff1a; if(test-condition)statement; 如果test-con…...

结构体,自定义类型

目录 结构体 结构体的声明 结构体的自引用 结构体的定义和初始化 结构体内存对齐 ​编辑 结构体的对齐规则&#xff1a; 为什么存在内存对齐&#xff1f; 修改默认对齐数 结构体传参 位段 什么是位段 位段的内存分配 位段的跨平台问题 枚举 联合&#xff08;共用体…...

Ubuntu22.04通过Maas和Juju部署openstack charm

目录 官方文档材料准备软件硬件 模板机和虚拟网络安装MAAS官方文档MAAS节点配置安装MAAS浏览器登录MAAS进行配置 激活DHCP 官方文档 https://docs.openstack.org/project-deploy-guide/charm-deployment-guide/2023.1/ 这是一个通过Maas面板即可部署openstack的方式&#xff0…...

老有所依:TSINGSEE青犀养老院智能视频监管方案

养老院智能监控方案是为了提高养老院内老人的安全和护理质量&#xff0c;利用智能技术与监控设备进行全方位的监控和管理&#xff0c;可以加强对老人的监护和护理&#xff0c;提高养老院的服务质量和安全性。 旭帆科技基于视频技术与AI智能分析技术构建的养老院智能视频监控方…...

vue中的this.$nextTick().then()

MENU 示例一示例二sortsplicepushrandomfloorMathwhile演示 示例一 let reorganize function (arr){let rest [];while (arr.length > 0) {let random Math.floor(Math.random() * arr.length);// 把获取到的值放到新定义的数组中rest.push(arr[random]);// 这句代码的作…...

Python处理Excel文件并与数据库匹配做拼接

Python处理Excel文件并与数据库匹配做拼接 需求&#xff1a;Python处理Excel中数据并于数据库交互匹配得到账号信息等其他操作 Python实现 import os import pandas as pd import pymssql import warnings import time# 提取速率函数 def extract_broadband_speed(speed):if…...

【出现模块node_modules里面包找不到】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 一、出现的问题二、解决办法三、其它可供参考 一、出现的问题 在本地运行 npm run docs:dev之后&#xff0c;出现 Error [ERR_MODULE_NOT_FOUND]: Cannot find package Z:\Blog\docs\node_modules\htmlparser2\ imported from Z:\Blo…...

高项备考葵花宝典-项目进度管理输入、输出、工具和技术(中,很详细考试必过)

项目进度管理的目标是使项目按时完成。有效的进度管理是项目管理成功的关键之一&#xff0c;进度问题在项目生命周期内引起的冲突最多。 小型项目中&#xff0c;定义活动、排列活动顺序、估算活动持续时间及制定进度模型形成进度计划等过程的联系非常密切&#xff0c;可以视为一…...

sql注入 [GXYCTF2019]BabySQli1

打开题目 多次尝试以后我们发现存在一个admin的账号&#xff0c;但是密码我们不知道 我们尝试一下万能密码 admin or 11 -- q 报错 我们尝试bp抓一下包看看 看着很像编码 先去base32解码 再base64解码 得到 我们从这个sql语句中得到注入点为name 根据报错信息我们知道是…...

python二维数组创建赋值问题:更改单个值却更改了所有项的值

test_list [] dic1 {} test_list [dic1 for _ in range(3)] ll [1, 2, 3]for i in range(3):test_list[i][value] ll[i]print(test_list)运行结果&#xff1a;每次赋值都更改了所有项 原因&#xff1a;python的二位数据创建方式就是这样&#xff0c;官方文档中有描述Wha…...

深度模型训练时CPU或GPU的使用model.to(device)

一、使用device控制使用CPU还是GPU device torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 单GPU或者CPU.先判断机器上是否存在GPU&#xff0c;没有则使用CPU训练 model model.to(device) data data.to(device)#或者在确定有GPU的…...

SpringBoot3-实现和注册拦截器

1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…...

Ubuntu 22.04源码安装yasm 1.3.0

sudo lsb_release -r看到操作系统的版本是22.04&#xff0c;sudo uname -r可以看到内核版本是5.15.0-86-generic&#xff0c;sudo gcc --version可以看到版本是11.2.0&#xff0c;sudo make --version可以看到版本是GNU Make 4.3。 下载yasm http://yasm.tortall.net/Downlo…...

LeetCode [中等]矩阵置零

73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 暴力解法 用两个标记数组分别记录每一行和每一列是否有零出现。 遍历该数组一次&#xff0c;如果某个元素为 0&#xff0c;那么就将该元素所在的行和列所对应标记数组的位置置为 true。再次遍历该数组&#xff0c;用标…...

十一、了解分布式计算

1、什么是&#xff08;数据&#xff09;计算&#xff1f; 2、分布式(数据)计算 &#xff08;1&#xff09;概念 顾名思义&#xff0c;分布式计算&#xff0c;即以分布式的形式完成数据的统计&#xff0c;得到需要的结果。 分布式数据计算&#xff0c;顾名思义&#xff0c;就是…...

数据结构和算法专题---2、算法思想

上文讲到算法的概念、复杂度&#xff0c;本文给大家介绍具体的算法思想&#xff0c;让大家对算法设计理念有个认识&#xff0c;后续再分别介绍各种算法。 算法思想 算法是解决问题的一种思想和方法&#xff0c;其基本思想是将一个复杂问题分解为多个简单的子问题&#xff0c;…...

在AWS Lambda上部署标准FFmpeg工具——自定义层的方案

大纲 1 确定Lambda运行时环境1.1 Lambda系统、镜像、内核版本1.2 运行时1.2.1 Python1.2.2 Java 2 打包FFmpeg3 创建Lambda的Layer4 测试4.1 创建Lambda函数4.2 附加FFmpeg层4.3 添加测试代码4.4 运行测试 参考文献 FFmpeg被广泛应用于音/视频流处理领域。对于简单的需求&#…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...