【Linux进程控制】自主Shell

目录

自主shell实现

获取基本变量

实现命令行

 获取用户命令字符串

命令行字符串分割

内建命令CD()

chdir

getcwd

putenv

检查是否为内建命令

 检查是否为重定向

执行命令

主函数设置

测试用例

项目代码


自主shell实现

根据之前学的内容,我们已经可以模拟一个shell的实现;一个shell程序需要循环做以下的事情: 1.获取命令行

2.解析命令行

3.创建子程序

4.替换子程序

5.父进程等待子进程退出

获取基本变量

首先我们根据shell发现我们要实现自助shell就要先获取以下变量

我们可以在env查看我们需要的变量,以及使用getenv()获取我们需要的变量 

//获取用户名
const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHome(){const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}

实现命令行

我们要把从env获取的内容拼成一个字符串

#define SIZE 512
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)void MakeCommandLineAndPrint(){char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();//只保留相对路径SkipPath(cwd);//写入指定缓冲区snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);//打印命令行printf("%s",line);fflush(stdout);
}

 获取用户命令字符串

#define SIZE 512
#define ZERO '\0'//获取用户指令
int GetUserCommand(char command[], size_t n){char* s = fgets(command, n, stdin);if(s == NULL) return -1;//由于fgets也会获取我们输入的回车换行符//为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'command[strlen(command)-1] = ZERO;return strlen(command);
}

命令行字符串分割

认识字符串分割函数

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

参数说明:

  • str:指向要分解的字符串的指针。在第一次调用时,这个参数应该是要分解的字符串;在随后的调用中,应该为 NULL,因为 strtok 会保存上一次的位置。

  • delim:包含分隔符的字符串。

函数工作原理:

  • strtok 在第一次调用时,会在字符串 str 中查找包含在 delim 中的任意一个字符,找到后将其替换为 null 字符 ('\0'),并返回指向该位置的指针。

  • 在随后的调用中,strtok 会从上次替换的 null 字符之后开始查找,重复同样的过程。

函数返回值:

  • 成功时,strtok 返回指向下一个标记的指针。

  • 当没有更多的标记时,返回 NULL

注意事项:

  • strtok 会修改原始字符串,因为它会在找到的分隔符位置放置 null 字符。

  • 使用 strtok 时,通常需要使用一个临时变量来保存原始字符串的副本,以避免破坏原始数据。

【示例代码】

#include <stdio.h>
#include <string.h>int main() {char str[] = "a-b-c-d-e";char *token;/* 获取第一个标记 */token = strtok(str, "-");/* 继续获取其他标记 */while (token != NULL) {printf("%s\n", token);token = strtok(NULL, "-");}return 0;
}

在这个例子中,字符串 "a-b-c-d-e" 将被分割为 "a", "b", "c", "d", 和 "e",每个部分由换行符分隔打印出来。

#define SEP " "
#define NUM 32
#define SkipSpace(cmd, pos) do{\while(1){\if(isspace(cmd[pos]))\pos++;\else break;\}\
}while(0)char *gArgv[NUM];//分割命令
void SplitCommand(char command[], size_t n){(void)n; // "ls -a -l -n" -> "ls" "-a" "-l" "-n"gArgv[0] = strtok(command, SEP);int index = 1;//故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL//刚好让gArgv最后一个元素是NULL, 并且while判断结束while((gArgv[index++] = strtok(NULL,SEP)));
}

内建命令CD()

我们需要让父进程执行CD命令,我们打印的命令行是父进程打印的,如果子进程执行cd命令是无法进行路径切换的

认识几个函数

chdir

在 C 语言中,chdir 函数用于更改当前工作目录。这个函数属于标准库中的 unistd.h 头文件。

int chdir(const char *path);

参数说明:

  • path:指向新工作目录路径的指针。

函数返回值:

  • 成功时,chdir 返回 0。

  • 出错时,返回 -1,并且设置 errno 来指示错误。

chdir 函数的行为会根据不同的操作系统和文件系统有所不同,但它通常用于更改进程的当前工作目录。更改当前工作目录后,所有相对路径的文件操作都将相对于新的工作目录进行。

getcwd

在 C 语言中,getcwd 函数用于获取当前工作目录的路径。这个函数定义在 unistd.h 头文件中,它返回一个指向字符串的指针,该字符串包含了当前工作目录的绝对路径。

char *getcwd(char *buf, size_t size);

参数说明:

  • buf:指向用于存储当前工作目录路径的缓冲区的指针。如果 bufNULL,则 getcwd 会分配足够大的缓冲区来存储路径,调用者必须使用 free 函数来释放这块内存。

  • size:缓冲区 buf 的大小。

函数返回值:

  • 成功时,getcwd 返回指向当前工作目录路径字符串的指针,该字符串存储在 buf 中。

  • 出错时,返回 NULL,并设置 errno 来指示错误。

putenv

在 C 语言中,putenv 函数用于添加或修改环境变量。环境变量是影响程序运行的外部参数,它们通常用于配置程序的行为。putenv 函数定义在 stdlib.h 头文件中。

int putenv(char *string);

参数说明:

  • string:指向一个形式为 “name=value” 的字符串的指针,其中 name 是环境变量的名称,value 是要赋予环境变量的值。

函数返回值:

  • 成功时,putenv 返回 0。

  • 出错时,返回非零值,并设置 errno 来指示错误。

使用 putenv 添加或修改环境变量后,这些更改仅对当前进程及其子进程有效,不会影响父进程或其他无关进程。

void Cd()
{const char *path = gArgv[1];if(path == NULL) path = GetHome();// path 一定存在chdir(path);// 刷新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}

★ps:如果不修改环境变量,则命令行上路径是变了,但打印的确没有变

检查是否为内建命令

char *gArgv[NUM];
int lastcode = 0;int CheckBuildin()
{int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}

 检查是否为重定向

void CheckRedir(char cmd[])
{// > >> <// "ls -a -l -n >  myfile.txt"int pos = 0;int end = strlen(cmd);while(pos < end){if(cmd[pos] == '>'){if(cmd[pos+1] == '>'){cmd[pos++] = 0;pos++;redir_type = App_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{cmd[pos++] = 0;redir_type = Out_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}}else if(cmd[pos] == '<'){cmd[pos++] = 0;redir_type = In_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{pos++;}}
}

执行命令

void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();else if(id == 0){//重定向设置if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename, O_RDONLY);dup2(fd, 0);}else if(redir_type == Out_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);}else if(redir_type == App_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{}}// childexecvp(gArgv[0], gArgv);exit(errno);}else{// fahterint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}}
}

主函数设置

int main(){int quit = 0;while(!quit){//1.我们需要自己输出一个命令行MakeCommandLineAndPrint(); //2.获取用户命令字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;//检测是否为重定向CheckRedir(usercommand);// 3. 命令行字符串分割. SplitCommand(usercommand, sizeof(usercommand));//4.检测是否为内建命令n = CheckBuildin();if(n) continue;// 5. 执行命令ExecuteCommand();}return 0;
}

测试用例

[wuxu@Nanyi myshell]$ gcc -o myshell myshell.c -std=c99
myshell.c: In function ‘Cd’:
myshell.c:152:2: warning: implicit declaration of function ‘putenv’ [-Wimplicit-function-declaration]putenv(cwd); // OK ^
[wuxu@Nanyi myshell]$ pwd
/home/wuxu/lesson20/myshell
[wuxu@Nanyi myshell]$ cd ..
[wuxu@Nanyi lesson20]$ pwd
/home/wuxu/lesson20
[wuxu@Nanyi lesson20]$ ls
log.txt  myshell  test  test1.c  test2.c
[wuxu@Nanyi lesson20]$ ls -a
.  ..  log.txt  myshell  test  test1.c  test2.c
[wuxu@Nanyi lesson20]$ ls -a -l
total 36
drwxrwxr-x  3 wuxu wuxu 4096 Sep  1 17:43 .
drwx------ 11 wuxu wuxu 4096 Sep  1 20:50 ..
-rw-rw-rw-  1 wuxu wuxu   50 Aug 31 19:49 log.txt
drwxrwxr-x  2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x  1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r--  1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r--  1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo $?
0
[wuxu@Nanyi lesson20]$ echo "hello wuxu" > log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu   11 Sep  1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo "hello Nanyi" >> log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu   23 Sep  1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ ^C
[wuxu@Nanyi lesson20]$ 

项目代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\while(1){\if(isspace(cmd[pos]))\pos++;\else break;\}\
}while(0)// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3int redir_type = None_Redir;
char *filename = NULL;// 为了方便,我就直接定义了
char cwd[SIZE*2];
char *gArgv[NUM];
int lastcode = 0;void Die()
{exit(1);
}//获取用户名
const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHome(){const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}void MakeCommandLineAndPrint(){char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();//只保留相对路径SkipPath(cwd);//写入指定缓冲区snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);//打印命令行printf("%s",line);fflush(stdout);
}//获取用户指令
int GetUserCommand(char command[], size_t n){char* s = fgets(command, n, stdin);if(s == NULL) return -1;//由于fgets也会获取我们输入的回车换行符//为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'command[strlen(command)-1] = ZERO;return strlen(command);
}//分割命令
void SplitCommand(char command[], size_t n){(void)n; // "ls -a -l -n" -> "ls" "-a" "-l" "-n"gArgv[0] = strtok(command, SEP);int index = 1;//故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL//刚好让gArgv最后一个元素是NULL, 并且while判断结束while((gArgv[index++] = strtok(NULL,SEP)));
}void ExecuteCommand(){pid_t id = fork();if(id < 0 ) Die();else if(id == 0){//重定向设置if(filename != NULL){if(redir_type == In_Redir){int fd = open(filename,O_RDONLY);dup2(fd,0);}}else if(redir_type == Out_Redir){int fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,0);}else if(redir_type == App_Redir){int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{}//childexecvp(gArgv[0],gArgv);exit(errno); }else{//fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){//wait success}}
}void Cd(){const char* path = gArgv[1];if(path == NULL) path = GetHome();//path 一定存在chdir(path);//刷新环境变量char temp[SIZE*2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK 
}int CheckBuildin(){int yes = 0;const char *enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n",lastcode);lastcode = 0;
}return yes;
}void CheckRedir(char cmd[]){// > >> <// "ls -a -l -n >  myfile.txt"int pos = 0;int end = strlen(cmd);while(pos < end){if(cmd[pos] == '>'){//是否为追加重定向if(cmd[pos+1] == '>'){//是追加重定向cmd[pos++] = 0;pos++;redir_type = App_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}else{//是输出重定向cmd[pos++] = 0;redir_type = Out_Redir;SkipSpace(cmd, pos);filename = cmd + pos;}}else if(cmd[pos] == '<')//是输入重定向{cmd[pos++] = 0;redir_type = In_Redir;SkipSpace(cmd,pos);filename = cmd + pos;}else{pos++;}}
}int main(){int quit = 0;while(!quit){//1.我们需要自己输出一个命令行MakeCommandLineAndPrint(); //2.获取用户命令字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;//检测是否为重定向CheckRedir(usercommand);// 3. 命令行字符串分割. SplitCommand(usercommand, sizeof(usercommand));//4.检测是否为内建命令n = CheckBuildin();if(n) continue;// 5. 执行命令ExecuteCommand();}ret

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

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

相关文章

【学习笔记】SSL/TLS安全机制之CAA

1、概念界定 CAA全称Certificate Authority Authorization&#xff0c;即证书颁发机构授权&#xff0c;每个CA都能给任何网站签发证书。 2、CAA要解决的问题 例如&#xff0c;蓝色网站有一张橙色CA颁发的证书&#xff0c;我们也知道还有许多其他的CA&#xff1b;中间人可以说服…

网址链接能做成二维码吗?在线网址二维码生成的操作技巧

现在用二维码能够展示很多的内容&#xff0c;将内容放入二维码后&#xff0c;通过扫码的方式获取内容会更加的方便快捷&#xff0c;简化获取内容的流程。比如在分享网上内容时&#xff0c;可以将链接生成二维码的方式来让用户扫码访问网页&#xff0c;那么网址转二维码具体该怎…

【BetterBench博士】2024年中国研究生数学建模竞赛 E题:高速公路应急车道紧急启用模型 问题分析

2024年中国研究生数学建模竞赛 E题&#xff1a;高速公路应急车道紧急启用模型 问题分析 更新进展 【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 E题&#xff1a;高速公路应急车道紧急启用…

【垃圾识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目选题+TensorFlow+图像识别

一、介绍 垃圾识别分类系统。本系统采用Python作为主要编程语言&#xff0c;通过收集了5种常见的垃圾数据集&#xff08;‘塑料’, ‘玻璃’, ‘纸张’, ‘纸板’, ‘金属’&#xff09;&#xff0c;然后基于TensorFlow搭建卷积神经网络算法模型&#xff0c;通过对图像数据集进…

Qt窗口——对话框

文章目录 对话框自定义对话框对话框分类消息对话框QMessageBox使用示例自定义按钮快速构造对话框 颜色对话框QColorDialog文件对话框QFileDialog字体对话框QFontDialog输入对话框QInputDialog 对话框 对话框可以理解成一个弹窗&#xff0c;用于短期任务或者简洁的用户交互 Qt…

2024华为杯研赛D题分析

2024华为杯研究生数学建模D题分析如下&#xff0c;完整版本在文末名片

【HTTP】请求“报头”,Referer 和 Cookie

Referer 描述了当前这个页面是从哪里来的&#xff08;从哪个页面跳转过来的&#xff09; 浏览器中&#xff0c;直接输入 URL/点击收藏夹打开的网页&#xff0c;此时是没有 referer。当你在 sogou 页面进行搜索时&#xff0c;新进入的网页就会有 referer 有一个非常典型的用…

扎克伯格的未来愿景:用智能眼镜引领数字社交新时代

Meta Connect 2024大会前夕&#xff0c;创始人马克扎克伯格的90分钟播客访谈&#xff0c;为我们描绘了Meta未来的蓝图。这场访谈&#xff0c;不仅是大会的热身&#xff0c;更是对科技未来的一次深刻洞察。 人工智能 - Ai工具集 - 未来办公人的智能办公生活导航网 扎克伯格的未…

nacos适配人大金仓的数据库

前言 在微服务架构中&#xff0c;服务发现和配置管理是关键组件。Nacos作为一个动态服务发现和配置管理平台&#xff0c;支持多种数据库作为其后端存储。本文将探讨如何在Nacos中适配人大金仓数据库&#xff0c;以及在此过程中的最佳实践。 Nacos简介 Nacos&#xff08;Nami…

二分查找算法(1) _二分查找_模板

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 二分查找算法(1) _二分查找模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 1. 二…

Redis——持久化策略

Redis持久化 Redis的读写操作都是在内存上&#xff0c;所以Redis性能高。 但是当重启的时候&#xff0c;或者因为特殊情况导致Redis崩了&#xff0c;就可能导致数据的丢失。 所以Redis采取了持久化的机制&#xff0c;重启的时候利用之间持久化的文件实现数据的恢复。 Redis提…

[Matplotlib教程] 02 折线图、柱状图、散点图教程

基于MFCC和CNN的语音情感识别 2 折线图、柱状图、散点图2.1 折线图2.1.1 简单折线图2.1.1 线形和Markevery2.1.2 带误差棒的折线图2.1.3 区间填充和透明度 2.2 柱状图2.2.1 分组柱状图2.2.2 堆叠柱状图2.2.3 横向柱状图 2.3 散点图 我们的网站是 菜码编程&#xff0c;我们的q群…

django项目添加测试数据的三种方式

文章目录 自定义终端命令Faker添加模拟数据基于终端脚本来完成数据的添加编写python脚本编写shell脚本执行脚本需要权限使用shell命令来完成测试数据的添加 添加测试数据在工作中一共有三种方式&#xff1a; 可以根据django的manage.py指令进行[自定义终端命令]可以采用第三方…

2024华为杯数学建模研赛F题建模代码思路文章研究生数学建模

截止2024.8.21 12点 已更新F全部小问的建模和问题一的代码 #### https://docs.qq.com/doc/DVVBUREF2SmFhRUl3F题: 问题1&#xff1a;卫星轨道根数与运动学关系的数学模型 从卫星的轨道根数计算出它在特定时刻的三维位置和速度。轨道根数包括&#xff1a; 1.计算卫星的轨道半径…

Android Studio开发发布教程

本文讲解Android Studio如何发布APP。 在Android Studiobuild菜单栏下点击Generate Singed Bundle/APK…打开对话框。 选择APK点击Next 点击Create New...进行创建

【赵渝强老师】K8s的DaemonSets控制器

DaemonSet控制器相当于在节点上启动了一个守护进程。通过使用DaemonSet可以确保一个Pod的副本运行在 Node节点上。如果有新的Node节点加入集群&#xff0c;DaemonSet也会自动给新加入的节点增加一个Pod的副本&#xff1b;反之&#xff0c;当有Node节点从集群中移除时&#xff0…

KMP整理+个人推导+快速上手理解

整理了一下KMP的写法&#xff1a; 这个是我自己写的&#xff08;个人推导&#xff0c;可能在时间复杂度上表现较弱&#xff0c;但是非常帮助初学者进行理解&#xff01;&#xff09; 下面是代码&#xff0c; ne 是next数组。我这个next数组表示的是&#xff1a; ne[i] : 当s…

Spring Boot框架在高校心理辅导中的实践

2 相关技术简介 2.1Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xff0c;任…

独立站内容营销SOP 1.0 丨出海笔记

提到内容营销&#xff0c;可能很多朋友都听过但没深入做&#xff0c;国内跨境独立站通过内容营销做的大流量的目前不多&#xff0c;哪怕大如 Shein, Anker&#xff0c;大部分时候还是在买量获客的阶段。 但大家只要明白一点即可&#xff1a;内容做得好不好&#xff0c;直接影响…

AD中的PCB的原点怎么设置?

在AD中&#xff0c;可以通过编辑元件的属性或者直接在PCB编辑器中设置原点来设置PCB或元件的原点。 对于PCB设计&#xff0c;你可以在PCB编辑器中直接设置原点。首先&#xff0c;你需要打开你的PCB设计文件。然后&#xff0c;在PCB编辑器中&#xff0c;选择“编辑”菜单下的“原…