Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux】手把手教你制作一个简易shell——(进程创建fork进程替换wait与进程等待exec的应用)(自定义shell程序设计)

【Linux】手把手教你制作一个简易shell——(进程创建fork进程替换wait与进程等待exec的应用)(自定义shell程序设计)

作者头像
YY的秘密代码小屋
发布于 2024-09-24 11:27:10
发布于 2024-09-24 11:27:10
18700
代码可运行
举报
文章被收录于专栏:C++系列C++系列
运行总次数:0
代码可运行

前言 大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++ Linux的老铁 主要内容含:

一.前置知识

【1】Shell和Bash简述

  • Shell 是一种命令行界面,是用户与系统之间的接口,允许用户执行命令来 管理系统资源、运行程序等
  • Bash 是 Shell 的一种实现,也是目前最流行的 Shell 之一

【2】Bash的输入原理——指针数组

  1. 我们运行Linux时会出现, bash提示符和命令行 ,我们接下来也要实现这两点
  2. 本质是通过 空格 作为分隔符,把一个一个字符串分隔开载入 指针数组中 ;
  3. 在父进程bash进程中,创建一个子进程,环境变量也会传递给子进程,并进行 进程等待wait
  1. 在子进程中通过 进程替换exec ,执行 指针数组中 中的命令(通过环境变量)

ifn<=0,直接结束省的创建子进程

cd就不行。因为是子进程的cd…

二.自定义shell程序设计

【1】<主函数模块>——大体框架

1.程序设计框架
  • 根据前置知识中的实现原理
  • 我们主函数中要有对应模块:
  1. 打印提示符&&获取用户命令字符串获取成功: getUserCommand函数
  2. 分割字符串: commandSplit函数
  3. 执行对应的命令: execute函数
2.程序设计细节
  1. 设置一个命令行获取字符数组:usercommand
  2. 设置一个存储———分割usercommand数组后的字符串的地址——的指针数组:argv
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define NUM 1024
#define SIZE 64

int main()
{
    while(1){  //shell要不停跑,死循环
    
        char usercommand[NUM];
        char *argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n= getUserCommand(usercommand, sizeof(usercommand));
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. 执行对应的命令
        execute(argv);
    }
}

【2】<打印提示符>模块

1.程序设计框架
  • 提示符信息包括:1.HOME 2.USER 3.HOSTNAME
  • 我们将上面3个信息分别封装,在getUserCommand函数中统一打印
  • 我们通过getenv函数可以获取 环境变量的地址,进而打印
  • command参数 接收命令行获取 字符数组usercommand
  • num参数 接收 字符数组长度
2.程序设计细节
  1. C语言默认会打开三个输入输出流:stdin键盘 stdout显示器stderr显示器,我们用到stdin获取输入流
  2. 不用scanf,用fget函数的原因:scanf遇到空格停下来,命令行本身就会出现空格。故采用行获取接口fgets
  1. command参数 接收命令行获取 字符数组usercommand ,我们输入命令后,最终你还是会输入\n——导致执行结果和shell之间出现空行;所以我们在输入完后要把command最后一个字符'\n'换成'\0'
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int getUserCommand(char *command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin); // 最终你还是会输入\n
    if(r == NULL) return -1;
    // "abcd\n" "\n"
    command[strlen(command) - 1] = '\0'; // 有没有可能越界?不会
    return strlen(command);
}

char *homepath()
{
    char *home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

const char *getUsername()
{
    const char *name = getenv("USER");
    if(name) return name;
    else return "none";
}
const char *getHostname()
{
    const char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}

【3】<分割字符串>模块

1.程序设计框架
  • 这个模块,我们要通过 空格 作为分隔符,把一个一个字符串分隔开载入 指针数组 argv
  • in参数 接收命令行获取 字符数组usercommand
  • *out[]参数 输出型参数,用于传出 分割usercommand数组后的字符串的地址——的指针数组argv
2.程序设计细节
  • 通过strstok函数分割; 注意语法,分成首次分割,和剩余分割
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define SEP " "
void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while( out[argc++] = strtok(NULL, SEP));
}

【4】<执行对应的命令>模块

1.程序设计框架

我们回顾原理部分:

  • 在父进程bash进程中,创建一个子进程,环境变量也会传递给子进程,并进行 进程等待wait
  • 在子进程中通过 进程替换exec ,执行 指针数组中 中的命令(通过环境变量)

于是我们设计出:

  1. fork函数创建子进程
  2. 子进程进行进程替换execvp函数,用到 分割usercommand数组后的字符串的地址——的指针数组argv
  3. 父进程等待子进程
2.程序设计细节

1. fork函数:

2. execvp函数: 由于我们用到了指针数组argv,所以用exec系列的vp尾缀,execvp 表示v(vector)数组,p(可以使用环境变量PATH,无需写全路径)

3. waitpid函数:不关心后续操作,status参数设置成NULL,options参数设置成0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
        // exec command
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;
        pid_t rid = waitpid(id, NULL, 0);
    }
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验