『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...

这篇具有很好参考价值的文章主要介绍了『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将学习一个强大的功能——程序替换。之前我们创建的子进程只能完成简单的一些任务且部分代码继承自父进程。有了程序替换以后,我们可以让子进程轻松的做更多的事情。学会了程序替换,我们可以编写一个简易的shell玩玩了,由此也可以对前几章的内容作复习与巩固~
『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++

🐧程序进程替换

我们一直在提子进程,那么创建子进程的目的是什么呢?无非是想让它帮助我们做某件事情。

fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支)。如果此时我们想要子进程执行一个全新的程序该怎么做呢?这就需要用到程序替换了~

🐦替换原理

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++

接下来我们就认识几个程序替换相关的函数,并演示如何操作。

🐦替换函数

一共有六种以exec开头的函数,称exec函数:

   #include <unistd.h> 		
   extern char **environ;

   int execl(const char *path, const char *arg, ...);
   int execlp(const char *file, const char *arg, ...);
   int execle(const char *path, const char *arg, ..., char * const envp[]);
   int execv(const char *path, char *const argv[]);
   int execvp(const char *file, char *const argv[]);
   int execvpe(const char *file, char *const argv[],char *const envp[]);
  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值

在这里,我们先以execl为例。

🔔示例1——execl

在代码中,我们尝试在子进程中进行程序替换,替换为ls指令。execl使用时:

  • 第一个参数是程序所在路径;
  • 剩下的参数为执行该程序时想要传递的命令行参数(简述:平时你在命令行中怎么用,就怎么传参);
  • 当确定想要传递的参数都给出后,一定要以NULL结尾。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();

  if(id == 0)
  {
    // 子进程
    execl("/bin/ls","ls","-a","-n","-l",NULL);
    printf("程序替换失败\n");
  }
  
  // 父进程
  printf("等待子进程成功,child_id:%d\n",wait(NULL));

  return 0;
}

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++
如图所示,结果正是我们想要的。

🐔观察与结论

根据对示例1的观察,我们发现:

  • 子进程中,execl替换后剩下的语句未执行(printf);
  • 子进程发生替换并未影响父进程;

由此我们可以得出结论:

  • 因为进程具有独立性,尽管父子进程刚开始用的是同一个代码和数据,但是当程序替换发生后,由于写时拷贝的存在,仅仅只是子进程的代码和数据被替换后的程序覆盖了,并不会影响父进程。
  • 程序替换函数,一旦替换发生,原来的代码在替换的语句执行后,就已经被新程序的代码和数据覆盖了,所以printf并未执行;
🐔函数命名理解

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++
其实四个函数的功能是类似的,都用于完成程序替换。只不过针对不同的场景,我们可以选择不同的函数。

这些函数根据函数名就大致可以判断如何使用:

  • l (list):表示参数采用列表 ;
  • v (vector) :参数用数组 ;
  • p (path) :有p自动搜索环境变量PATH
  • e (env) :表示自己维护环境变量;

🔔其余函数示例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
  extern char** environ;
  pid_t id = fork();

  if(id == 0)
  {
    // 子进程
	
	char* const myargv[] = {"ls","-a","-l",NULL};
 	// 带l的,需要跟上路径
    execl("/bin/ls","ls","-a","-n","-l",NULL);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execlp("ls","ls","-a","-l",NULL);
    // 带V的,可以使用自己的参数列表数组
    execvp("ls",myargv);
    // 带e的,需要自己组装环境变量
    execvpe("ls",myargv,environ);
 
    printf("程序替换失败\n");
  }
  
  // 父进程
  printf("等待子进程成功,child_id:%d\n",wait(NULL));

  return 0;
}

有了前几章所讲知识以及在、本章程序替换部分的知识,我们可以试着自己实现一个简易的shell

🐧myshell编写

🔔代码展示

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

// 将输入的字符串切割并保存到argv中
int split(char* commandstr,char* argv[])
{
  assert(commandstr);
  assert(argv);

  argv[0] = strtok(commandstr,SEP);
  if(argv[0] == NULL) return -1; // 若为NULL,则重新输入
  int i = 1;
  while(argv[i++] = strtok(NULL,SEP));
  return 0;
}

int main()
{
  while(1)
  {
    char commandstr[MAX] = {0}; // 用于保存用户输入的指令
    char* argv[ARGC] = {NULL};
    
    printf("[hxy@mychaimachine]$ ");
    fflush(stdout);
    char* s = fgets(commandstr,sizeof(commandstr),stdin); // 获取指令
    assert(s);
    (void)s;

    commandstr[strlen(commandstr)-1] = '\0'; // 去掉键盘输入的\n

    int n = split(commandstr,argv); // 切割输入的指令字符串
    if(n!=0) continue;

    pid_t id = fork();
    if(id == 0)
    {
      // 子进程
      execvp(argv[0],argv); // 程序替换
      exit(1);
    }

    int status = 0;
    waitpid(id,&status,0); // 等待子进程
  }
  return 0;
}

🔔效果展示

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++
如图所示,我们用简短的50行代码写了一个简易的shell(命令行解释器)。其实Linux源码中的内容可远不止这些,而且我们实现的shell所能实现的功能非常少,非常简陋。例如,ls 并没有对不同的文件“上色”。

接下来,我们可以完善上述的代码,继续添加一些小功能。

🐧myshell_plus

上文中的myshell是非常简陋的,有许多指令诸如:cdexportenv等指令并不能正确执行。

就用cd来举例,myshell执行指令其实是交给子进程去做的,子进程的执行结果并不会影响父进程。也就是说,cd指令需要mybash自己去执行。

  • 我们把让bash自己执行的命令叫作内建命令。我们之前学到过的几乎所有关于环境变量的命令都是内建命令

于是,我们可以在创建子进程之前用if做判断,若用户输入的指令为内建命令,则让父进程执行该指令。

🔔代码展示

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr,char* argv[])
{
  assert(commandstr);
  assert(argv);

  argv[0] = strtok(commandstr,SEP);
  if(argv[0] == NULL) return -1;
  int i = 1;
  while((argv[i++] = strtok(NULL,SEP)));
  return 0;
}

void showEnv()
{
  extern char** environ;
  for(int i = 0; environ[i]; i++) printf("%d:%s\n",i,environ[i]);
}

int main()
{
  extern int putenv(char* string);
  char myenv[32][256];
  int env_index = 0;
  int exitCode = 0;

  while(1)
  {
    char commandstr[MAX] = {0};
    char* argv[ARGC] = {NULL};
    
    printf("[hxy@mychaimachine]$ ");
    fflush(stdout);
    char* s = fgets(commandstr,sizeof(commandstr),stdin);
    assert(s);
    (void)s;

    commandstr[strlen(commandstr) - 1] = '\0'; // 去掉键盘输入的\n

    int n = split(commandstr,argv); // 切割字符串
    if(n != 0) continue;
    
    if(strcmp(argv[0],"cd") == 0)
    {
      if(argv[1] != NULL) chdir(argv[1]);
      continue;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
      if(argv[1] != NULL)
      {
        strcpy(myenv[env_index],argv[1]); // 用户自己定义的环境变量,需要bash自己来维护
        putenv(myenv[env_index++]);
      }
      continue;
    }
    else if(strcmp(argv[0],"env") == 0)
    {
      showEnv(); // env查看环境变量时,其实看的是父进程bash的变量
      continue;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
      const char* target_env = NULL;
      if(argv[1][0] == '$')
      {
        if(argv[1][1] == '?')
        {
          printf("%d\n",exitCode);
          continue;
        } 
        else target_env = getenv(argv[1] + 1);

        if(target_env != NULL) printf("%s = %s\n",argv[1] + 1,target_env);
      }
      continue;
    }

    // ls设置颜色选项
    if(strcmp(argv[0],"ls") == 0)
    {
      int pos = 0;
      while(argv[pos] != NULL)
      {
        pos++;
      }
      argv[pos++] = (char*)"--color=auto";
      argv[pos] = NULL;
    }
        

    pid_t id = fork();
    if(id == 0)
    {
      // 子进程
      execvp(argv[0],argv);
      exit(1);
    }

    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret > 0)
    {
      exitCode = WEXITSTATUS(status); // 获取最近一次进程的退出码
    }
  }
  return 0;
}

🔔效果展示

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++
本章的内容就到这里了,觉得对你有帮助的话就支持一下博主把~

『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...,Linux从入门到精通,linux,运维,c++

点击下方个人名片,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓文章来源地址https://www.toymoban.com/news/detail-619434.html

到了这里,关于『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩...的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 精通 JavaScript 数据处理大全:手写代码从入门到精通

    ​🌈个人主页:前端青山 🔥系列专栏:JavaScript篇 🔖 人终将被年少不可得之物困其一生 依旧 青山 ,本期给大家带来JavaScript篇专栏内容:JavaScript- 数据处理 目录 1. 实现日期格式化函数 2. 交换a,b的值,不能用临时变量 3. 实现数组的乱序输出 4. 实现数组元素求和 5. 实现数组

    2024年02月03日
    浏览(32)
  • 【Linux从入门到精通】C语言模拟实现进度条小程序

        在Linux下,我们安装软件时会经常看到进度条,来告知我们安装的进度。我们不妨自己模拟实现一个进度条,看看其中的细节。模拟实现进度条并不困难,但其中的细节我们又不可忽视。 本篇文章会对模拟实现进度条进行详解 。 文章目录 一、进度条整体模板 二、输出缓

    2024年02月03日
    浏览(31)
  • PHP从入门到精通—PHP开发入门-PHP概述、PHP开发环境搭建、PHP开发环境搭建、第一个PHP程序、PHP开发流程

    每开始学习一门语言,都要了解这门语言和进行开发环境的搭建。同样,学生开始PHP学习之前,首先要了解这门语言的历史、语言优势等内容以及了解开发环境的搭建。 PHP概述 Ø 认识PHP PHP最初是由Rasmus Lerdorf于1994年为了维护个人网页而编写的一个简单程序。这个程序用来显

    2024年02月14日
    浏览(46)
  • 【100天精通python】Day1:python入门_初识python,搭建python环境,运行第一个python小程序

     目录 专栏导读  1 初始python python 概述 python的应用领域   应用python的公司  2 搭建python 开发环境  2.1 安装python(以windows 系统为例)(1)下载安装包  (2) 下载保存后打开文件夹点击以管理员身份运行  (3)选择自定义安装,同时将add python3.9 to path勾上。  (4)测试

    2024年02月15日
    浏览(31)
  • Linux Kernel入门到精通系列讲解(QEMU-虚拟化篇) 2.1 新增加一个RISC-V CPU(NARUTO-PI)

    上一章节我们讲解了开源的 QEMU 开发板怎么启动,从这章节开始,我们将会亲手去从无到有开发一个 CPU ,它包括 CPU Core , Memory Device , Communication Controller 和 Device 等等。 注意,本章节中调用的很多自定义宏都在 include/hw/riscv/naruto.h 文件,这里我就不展开说了,大家下载我

    2024年04月25日
    浏览(31)
  • 从入门到精通,30天带你学会C++【第五天:刷题软件的推荐及使用教程】(学不会你找我)

    目录 前言 刷题软件推荐 1、洛谷 2、OpenJudge 3、LeetCode 4、POJ 洛谷网站的使用方法 结尾 上期投票我定的时间太长了,相信很多人都等不及了,那么我就提前截止一下,抱歉。 在这也感谢大家参与! 截图时间:2023.9.1.21:56 投票结果是推荐刷题软件,那么今天课程他来了! 网址

    2024年02月10日
    浏览(33)
  • 35.从入门到精通:Python CGI编程 什么是CGI 网页浏览 CGI架构图 Web服务器支持及配置 第一个CGI程序 HTTP头部

    CGI是一种通用网关接口,它是一种标准的协议,用于在Web服务器上运行外部程序(通常是脚本程序) 。CGI程序可以在Web服务器上生成动态内容,例如网页表单的处理、数据库查询和图像生成。CGI程序可以用多种编程语言编写,例如Python、Perl、C++等。 在Python中,CGI编程可以使

    2024年02月12日
    浏览(30)
  • MATLAB | 从入门到精通MATLAB必去的10大网站,一个比一个经典

    从入门到精通MATLAB必去的10大网站 Help Center: https://ww2.mathworks.cn/help/index.html 官方推出的集参考文档、程序示例、函数集合、视频简介、疑难解答于一体的综合学习平台 File Exchange: https://ww2.mathworks.cn/matlabcentral/fileexchange 官方推出的matlab程序共享平台,有很多大神的源代码 Blo

    2023年04月09日
    浏览(37)
  • 学会Python开发的第一步:写一个桌面小程序

    嗨喽,大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ 当使用桌面应用程序的时候,有没有那么一瞬间, 想学习一下桌面应用程序开发? 建议此次课程大家稍作了解不要浪费太多时间, 因为没有哪家公司会招聘以为Python程序员开发桌面程序吧? Python 3.6 Python是一种代

    2024年02月03日
    浏览(47)
  • 【Linux】教你用进程替换制作一个简单的Shell解释器

    本章的代码可以访问这里获取。 由于程序代码是一体的,本章在分开讲解各部分的实现时,代码可能有些跳跃,建议在讲解各部分实现后看一下源代码方便理解程序。 我们想要制作一个简单的 Shell 解释器,需要先观察Shell是怎么运行的,根据 Shell 的运行状态我们再去进行模

    2024年02月02日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包