【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

页内跳转

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

相关文章
|
5天前
|
Shell Linux 程序员
【Linux】Shell 命令以及运行原理
【Linux】Shell 命令以及运行原理
|
6天前
|
Shell Linux
【linux课设】自主实现shell命令行解释器
【linux课设】自主实现shell命令行解释器
|
6天前
|
存储 Unix Linux
linux权限管理以及shell
linux权限管理以及shell
|
7天前
|
运维 Linux Shell
day02-Linux运维-系统介绍与环境搭建_硬件 系统核心 解释器shell 外围操作系统
day02-Linux运维-系统介绍与环境搭建_硬件 系统核心 解释器shell 外围操作系统
|
12天前
|
Shell Linux Perl
Linux|如何允许 awk 使用 Shell 变量
Linux|如何允许 awk 使用 Shell 变量
22 2
|
12天前
|
网络协议 Shell Linux
LabVIEW 在NI Linux实时设备上访问Shell
LabVIEW 在NI Linux实时设备上访问Shell
17 0
|
12天前
|
Shell Linux
【Linux】进程实践项目(更新中) — 自主shell编写
前几篇文章,我们学习进程的相关知识:进程概念,进程替换,进程控制。熟悉了进程到底是个什么事情,接下来我们来做一个实践,来运用我们所学的相关知识。这个项目就是手搓一个shell模块,模拟实现Xshell中的命令行输入。
16 1
|
12天前
|
Shell Linux 信息无障碍
5 个有用的 Linux Shell 转义序列
5 个有用的 Linux Shell 转义序列
|
12天前
|
Shell Linux 编译器
C语言,Linux,静态库编写方法,makefile与shell脚本的关系。
总结:C语言在Linux上编写静态库时,通常会使用Makefile来管理编译和链接过程,以及Shell脚本来自动化构建任务。Makefile包含了编译规则和链接信息,而Shell脚本可以调用Makefile以及其他构建工具来构建项目。这种组合可以大大简化编译和构建过程,使代码更易于维护和分发。
31 5
|
12天前
|
Linux Shell 程序员
【Linux】权限(shell运行原理、概念,Linux权限)
【Linux】权限(shell运行原理、概念,Linux权限)
18 2