【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

页内跳转

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

相关文章
|
6天前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
6天前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
10天前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
26 2
|
1月前
|
Shell Linux 开发工具
linux shell 脚本调试技巧
【9月更文挑战第3天】在Linux中调试shell脚本可采用多种技巧:使用`-x`选项显示每行命令及变量扩展情况;通过`read`或`trap`设置断点;利用`echo`检查变量值,`set`显示所有变量;检查退出状态码 `$?` 进行错误处理;使用`bashdb`等调试工具实现更复杂调试功能。
|
2月前
|
JavaScript 关系型数据库 Shell
Linux shell编写技巧之随机取字符串(一)
本文介绍了Linux Shell脚本的编写技巧,包括环境配置、变量命名规则和缩进语法,并提供了一个实例练习,展示如何使用`$RANDOM`变量和`md5sum`命令来生成随机的8位字符串。
37 4
|
2月前
|
Ubuntu Linux Shell
在Linux中,如何使用shell脚本判断某个服务是否正在运行?
在Linux中,如何使用shell脚本判断某个服务是否正在运行?
|
2月前
|
Shell Linux 网络安全
在Linux中,如何利用Shell把10台主机的当前时间写到一个文件里边?
在Linux中,如何利用Shell把10台主机的当前时间写到一个文件里边?
|
2月前
|
Java Shell Linux
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
关于如何使用Shell脚本来解析Linux系统中的应用服务日志,提供了脚本实现的详细步骤和技巧,以及一些Shell编程的技能扩展。
31 0
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
|
2月前
|
监控 Shell Linux
在Linux中,如何使用shell脚本进行系统监控和报告?
在Linux中,如何使用shell脚本进行系统监控和报告?
|
2月前
|
Shell Linux
在Linux中,shell脚本中的条件语句和循环结构是什么?
在Linux中,shell脚本中的条件语句和循环结构是什么?
下一篇
无影云桌面