Redian新闻
>
Linux 实现简易的 Shell 命令行解释器

Linux 实现简易的 Shell 命令行解释器

公众号新闻

来自:入门小站

一、前言

来制作一个简易的 [Shell 命令]行解释器。

首先这是与 Shell 的互动::

用下图的[时间轴]来表示事件的发生次序。其中时间从> > 左向右。shell 由标识为 sh 的方块代表,它随着时间的流逝从左向右移动。shell 从用户读入字符串 "ls"。shell 建立一个新的进程,然后在那个进程中运行 ls 程序并等待那个进程结束。

然后 shell 读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个 shell,需要循环以下过程:

  • 1. 获取命令行
  • 2. 解析命令行
  • 3. 建立一个子进程(fork)
  • 4. 替换子进程(execvp)
  • 5. 父进程等待子进程退出(wait)

二、准备工作

1.输出提示符

这里的提示字符为用户名 @主机名 当前路径# 直接打印出来作为提示所用

printf("用户名@主机名 当前路径#");

这里没有 \ n,会有缓冲区的问题,类似于我们之前所说的进度条所遇到的问题,可以用 fflush(stdout) 刷新缓冲区。

2. 输入和获取命令

输入

我们需要输入一连串命令,其中可能出现空格,所以不能使用 gets 函数,需要用到 fgets 函数,同时,可以定义一个 lineCommand[NUM] 数组

#define NUM 1024
char lineCommand[NUM];
char* s = fgets(lineCommand,sizeof(lineCommand) - 1, stdin);
assert(s != NULL);

但是打印的时候却多换了一行,这是我们把 \ n 也读取到了,直接进行处理即可, 清除最后一个 \ n

lineCommand[strlen(lineCommand) - 1] = 0;

可以通过打印看看效果和测试是否有 BUG

printf("test:%s\n",lineCommand);

获取

输入之后,我们自然需要去进行获取,我们需要分割命令行,这个地方用 strtok。把字符串切割成若干个子串:
strtok: 第一次直接传递参数,第二次则必须传 NULL。且在最终 strtok 会返回 NULL。

3.shell 运行原理

同时,在理解一下 shell 的运行原理:shell 内部提取命令行做分析,然后调用 exec. shell 执行命令必须通过创建子进程,如果不创建子进程会把我们所有的 shell 全部替换,所以执行命令时一般磁盘上的程序必须创建子进程。

4. 内建命令

我们在运行自己写的 shell 的时候,发现输入 cd … 输入 cd path 等命令时发现路径并没有改变!

没有发生改变是因为自己写的 shell 执行很多命令都要 fork() 创建子进程,让子进程执行的 cd,子进程有自己的工作目录,所以更改的子进程的目录,子进程执行完毕,继续用的是父进程,既 shell,并没有影响父进程,所以并没有改变。

对于 cd, 我们可以采用内建命令:不需要创建子进程执行,让 shell 自己执行命令,称为内建命令。本质就是执行系统接口,我们可以调用一个系统接口 chdir,可解决上述问题:


5. 替换

采用 execvp 进行替换进程

pid_t id = fork();
assert(id != -1);
if(id == 0)
{
 execvp(myargv[0],myargv);
 exit(1);
}

三、整体代码

 #include<stdio.h>
 #include<stdlib.h>
 #include<unistd.h>
 #include<sys/types.h>
 #include<sys/wait.h>
 #include<assert.h>
 #include<string.h>
 #define NUM 1024
 #define OPT_NUM 64
 char lineCommand[NUM];
 char *myargv[OPT_NUM];//指针数组
 int lastcode = 0;
 int lastsig = 0;
 int main()
 {
     while(1)
     {
         // 1.输出提示符
         printf("lj@VM-8-2-centos 当前路径#");
         fflush(stdout);
         // 2.获取用户输入的命令,输入的时候,用户最后还输入了\n
         char* s = fgets(lineCommand,sizeof(lineCommand) - 1, stdin);
         assert(s != NULL);
         (void)s; //避免Linux认为s变量未使用,导致警告
         // 清除最后一个\n;例如:abcd\n
         lineCommand[strlen(lineCommand) - 1] = 0;
         //printf("test:%s\n",lineCommand);
         // "ls -a -l -i" -->字符串分割-->"ls" "-a" "-l" "-i"
         myargv[0] = strtok(lineCommand, " ");
         int i = 1;
         if(myargv[0] != NULL && (strcmp(myargv[0],"ls") == 0))
         {
             myargv[i++] = (char*)"--color=auto";
         }
         //如果没有子串了,strtok会返回NULL,即myargv[end] = NULL
         while(myargv[i++] = strtok(NULL," "));
         //如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
         //像这种不需要让我们的子进程来执行,而是让shell自己执行的命令—内建命令
         //其中echo是一个自建命令
         if(myargv[0] != NULL && (strcmp(myargv[0],"cd") == 0))
         {
             if(myargv[1] != NULL) chdir(myargv[1]);
             continue;
         }
         if(myargv[0] != NULL && myargv[1] != NULL && (strcmp(myargv[0],"echo") == 0))
         {
             if(strcmp(myargv[1],"$?") == 0)
             {
                 printf("%d,%d\n",lastcode,lastsig);
             }
             else
             {
                 printf("%s\n",myargv[i]);
             }
             continue;
         }
         //利用条件编译测试是否成功
 #ifdef DEBUG
         for(int i = 0; myargv[i]; ++i)
         {
             printf("myargv[%d]:%s\n",i,myargv[i]);
         }
 #endif
         //执行命令
         pid_t id = fork();
         assert(id != -1);
         if(id == 0)
         {
             execvp(myargv[0],myargv);
             exit(1);
         }
         int status = 0;
         pid_t ret = waitpid(id,&status,0);
         assert(ret > 0);
         (void) ret;
         lastcode = (status >> 8) & 0xFF;
         lastsig = status & 0x7F;
     }
     return 0;
 }

END

官方站点:www.linuxprobe.com

Linux命令大全:www.linuxcool.com

刘遄老师QQ:5604241

Linux技术交流群:3762708

(新群,火热加群中……)

想要学习Linux系统的读者可以点击"阅读原文"按钮来了解书籍《Linux就该这么学》,同时也非常适合专业的运维人员阅读,成为辅助您工作的高价值工具书!


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Fish Shell计划采用Rust重写教你如何用 10 行 bash shell 脚本监控 Linux?Agustín Hernández:中美洲建筑背景下的未来主义巨构在台湾和台湾人谈台湾, 说民主天赋“易昺(bǐng)”,创造历史!Linux 6.1 内核被批准为长期支持版本 | Linux 中国lnav: 用于 Linux 的高级日志文件浏览器 | Linux 中国钓鱼热点 89号桥Chinese Gamers Brace for Adventure as the New Zelda Hits Shelves中国人是不是很怪?40 个简单又有效的 Linux Shell 脚本示例!Shell 分析服务器日志命令总结再说说龙泉寺(四)SpringBoot 应用的新命令行界面:Just在 Linux 命令行上使用 dict 文字工具 | Linux 中国收藏夹吃灰!2 万字系统总结,带你实现 Linux 命令自由~使用 ChatGPT AI 从英文文本生成 Linux 命令 | Linux 中国GNOME 2 的 Linux 文件管理器 Caja | Linux 中国10个有趣的 Linux Shell 脚本 “面试和解答”,老司机也可能 “翻车” ?Linux ——超级漂亮的 ShellJiangsu City Punishes Property Developers For Selling CheaplyTUXEDO Stellaris 16(Gen5)是目前所能找到的终极 Linux 笔记本电脑 | Linux 中国纯命令行+美观UI,10款实用开源下载工具2023 年十佳 Linux 服务器发行版 | Linux 中国用ChatGPT秒建大模型!OpenAI全新插件杀疯了,接入代码解释器一键get在 C 语言中使用 getopt 解析命令行短选项 | Linux 中国5 个有用的 Linux Shell 转义序列 | Linux 中国Just:Spring Boot 应用的新命令行界面世界上只有两个 Linux 发行版:Arch Linux 与其它 | Linux 中国GIF动画渲染、让灯塔闪烁、创建航空动态图……ChatGPT代码解释器插件「不止于代码」zip 命令的解释与示例 | Linux 中国服务器推送事件:一种从服务器流式推送事件的简易方法 | Linux 中国5 个适合视力障碍者的 Linux 发行版 | Linux 中国阶级固化时代还能有阶层晋升吗40 个简单又有效的 Linux Shell 脚本示例
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。