【Linux】12. 模拟实现shell

简介: 【Linux】12. 模拟实现shell

回顾

在之前的学习过程中,我们掌握了进程的相关概念,冯诺依曼体系结构,进程地址空间概念,进程状态,进程控制,进程退出,进程替换......等等一系列的基础知识,这些基础知识让我们清楚的知道进程从加载到运行到接收退出信息,操作系统是如何一步步进行控制的,也了解到相关操作系统的相关概念。
我们还学习到通过进程替换的方式,让操作系统使子进程执行另一个全新的进程却并未创建子进程的操作
在这里插入图片描述
废话不多说,直接开整!

1. 输出提示符

[hx@VM-12-2-centos myshell]$ cat myshell.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>

int main()
{
   
   
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);
  sleep(10);
  return 0;
}

因为printf当中未带\n,所以要fflush(强制刷新缓冲区,将缓冲区的数据读取到显示器上)
在这里插入图片描述

2. 获取用户输入

在这里插入图片描述

3. 解决输入结尾带\n的问题

[hx@VM-12-2-centos myshell]$ cat myshell.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>

#define NUM 1024
char lineCommand[NUM];

int main()
{
   
   
  // 输出提示符
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);

  // 获取用户输入
  char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
  assert(s != NULL);
  // 清除最后一个\n
  lineCommand[strlen(lineCommand)-1] = 0;
  printf("test:%s\n",lineCommand);
  return 0;
}

在这里插入图片描述

4. 字符串切割

在这里插入图片描述

5. 测试切割是否成功

采用条件编译的方式进行测试,后续不想要测试随时取消#define定义值即可

[hx@VM-12-2-centos myshell]$ cat myshell.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>

#define NUM 1024
#define OPT_NUM 64

char lineCommand[NUM];
char *myargv[OPT_NUM];

int main()
{
   
   
  // 输出提示符
  printf("用户名@主机名 当前路径# ");
  fflush(stdout);

  // 获取用户输入
  char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
  assert(s != NULL);
  // 清除最后一个\n
  lineCommand[strlen(lineCommand)-1] = 0;
  // printf("test:%s\n",lineCommand);

  // 字符串切割
  myargv[0] = strtok(lineCommand," ");
  int i = 1;
  while(myargv[i++] = strtok(NULL," "));

  //测试切割是否成功
#ifdef DEBUG 
  // myargv数组中最后1个是NULL 正好拿来判断循环结束
  for(int i = 0; myargv[i];i++)
  {
   
   
    printf("myargv[%d]:%s\n",i,myargv[i]);
  }
#endif
  return 0;
}

在这里插入图片描述

6. 嵌套循环

在这里插入图片描述

7. 执行命令

在这里插入图片描述
所以,到这里我们也就知道了shell底层执行命令行的原理:通过提取命令行上的指令对其分析(切割子串),再通过exec*系列接口调用所对应的命令来显示到显示器上。
乍一看好像已经实现完成了,还存在什么缺陷嘛?
在这里插入图片描述
ls在执行时并没有颜色上的区分

8. ls -- 添加颜色

在这里插入图片描述

9. 内建命令cd

9.1 当前路径的认识

在这里插入图片描述
在这里插入图片描述
==但这同样没有解决myshell当中cd切换不了路径的问题鸭,那么该如何理解呢?==
shell在进行执行时,都是先通过fork创建子进程来执行对应的命令,那么也就是子进程执行的cd命令,子进程也存在自己的工作目录,当我们进行cd命令时更改的是子进程我的工作目录,当子进程执行完毕时,由父进程接收其返回值,之后使用的还是父进程shell,所以我们在父进程shell中观察不到子进程切换路径的现象

说了这么多,该如何操作实现呢?

9.2 增加内建命令cd的判断

在这里插入图片描述
==对于这种不需要子进程执行,而是让shell执行的命令,我们称之为内建/内置命令==

10. 内建命令echo $?

在这里插入图片描述

11. 最终代码

如下所示:

[hx@VM-12-2-centos myshell]$ cat myshell.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.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){
   
   
    // 输出提示符
    printf("用户名@主机名 当前路径# ");
    fflush(stdout);

    // 获取用户输入
    char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
    assert(s != NULL);
    // 清除最后一个\n
    lineCommand[strlen(lineCommand)-1] = 0;
    // printf("test:%s\n",lineCommand);

    // 字符串切割
    myargv[0] = strtok(lineCommand," ");
    int i = 1;
    if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
    {
   
   
      myargv[i++] = (char*)"--color=auto";
    }

    while(myargv[i++] = strtok(NULL," "));

    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[1]);
      }
      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(100);
    }
    // 父进程
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    assert(ret>0);
    lastCode = ((status>>8) & 0xFF);
    lastSig = (status & 0x7F);
  }
  return 0;
}
[hx@VM-12-2-centos myshell]$ cat Makefile 
myshell:myshell.c
    gcc -o myshell myshell.c -std=c99 # -DDEBUG

.PHONY:clean
clean:
    rm -f myshell

页内跳转

点击它跳转到 标题-页内跳转

相关文章
|
3月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
24天前
|
存储 Shell Linux
Linux 如何更改默认 Shell
Linux 如何更改默认 Shell
30 0
Linux 如何更改默认 Shell
|
2月前
|
Web App开发 网络协议 Linux
linux命令总结(centos):shell常用命令汇总,平时用不到,用到就懵逼忘了,于是专门写了这篇论文,【便持续更新】
这篇文章是关于Linux命令的总结,涵盖了从基础操作到网络配置等多个方面的命令及其使用方法。
78 1
linux命令总结(centos):shell常用命令汇总,平时用不到,用到就懵逼忘了,于是专门写了这篇论文,【便持续更新】
|
1月前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
3月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
3月前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
749 2
|
2月前
|
存储 Shell Linux
【Linux】shell基础,shell脚本
Shell脚本是Linux系统管理和自动化任务的重要工具,掌握其基础及进阶用法能显著提升工作效率。从简单的命令序列到复杂的逻辑控制和功能封装,Shell脚本展现了强大的灵活性和实用性。不断实践和探索,将使您更加熟练地运用Shell脚本解决各种实际问题
36 0
|
3月前
|
Shell Linux 开发工具
linux shell 脚本调试技巧
【9月更文挑战第3天】在Linux中调试shell脚本可采用多种技巧:使用`-x`选项显示每行命令及变量扩展情况;通过`read`或`trap`设置断点;利用`echo`检查变量值,`set`显示所有变量;检查退出状态码 `$?` 进行错误处理;使用`bashdb`等调试工具实现更复杂调试功能。
|
4月前
|
JavaScript 关系型数据库 Shell
Linux shell编写技巧之随机取字符串(一)
本文介绍了Linux Shell脚本的编写技巧,包括环境配置、变量命名规则和缩进语法,并提供了一个实例练习,展示如何使用`$RANDOM`变量和`md5sum`命令来生成随机的8位字符串。
64 4
|
4月前
|
监控 Shell Linux
在Linux中,如何使用shell脚本进行系统监控和报告?
在Linux中,如何使用shell脚本进行系统监控和报告?