【Linux——实现一个简易shell】

news2024/12/1 7:17:55

黑暗中的我们都没有说话,你只想回家,不想你回家...............................................................

文章目录

前言

一、【shell工作过程】

二、【命令行参数】

2.1、【获取命令行参数】

1、【输出命令行提示符】

2、【输入命令行参数】

2.2、【解析命令行参数】

1、【分割命令行参数】

2、【判断指令类型】

3、【外部命令的执行】

4、【内建命令的执行】

cd命令:

export指令:

echo指令:

5、【实现重定向】

三、【总结说明以及完整代码】

1、【使shell循环工作】

2、【代码中的全局变量说明】

3、【完整代码及效果演示】

总结


前言

shell也就是命令行解释器,其运行原理就是:当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可,学习了进程的概念及其控制以后,我们也可以上手做一个简易的shell。


一、【shell工作过程】

我们一般见到的shell一般是下面这个样子:

然后我们可以在光标之后输入一些命令来完成相应的操作:

那么我们来想一下,shell是如何进行工作的呢?

首先我们看到的命令行提示符,光标,这些我们只需要做一些打印工作即可。

但是那些命令是如何执行的呢?

我们知道那些命令实际上也是一个个存放在磁盘中的可执行程序,我们通过在shell界面上输入那些命令名,实际上就是将其从磁盘加载到内存进而进行调用,也就是说,我们要在shell进程里创建子进程,然后通过子进程去调用那些命令的可执行程序,才能完成使用命令的操作。

因此shell需要执行的逻辑其实非常简单,其只需循环执行以下步骤:

  1. 获取命令行。
  2. 解析命令行。
  3. 创建子进程。
  4. 替换子进程。
  5. 等待子进程退出。

其中,创建子进程使用fork函数,替换子进程使用exec系列函数,等待子进程使用wait或者waitpid函数。

下面我们具体来看一看。

二、【命令行参数】

2.1、【获取命令行参数】

1、【输出命令行提示符】

我们首先将shell界面中的命令行提示符打印出来,首先我们先来看看这些命令行提示符都是什么:

不难发现这些都是我们shell环境变量中的值,所以我们可以通过函数getenv(),来获取他们具体如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define NUM 1024   //用来存放输入的数组大小 

//获取用户名
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";
  }
}
//获取工作目录
const char* getCwd()
{
  const char* cwd=getenv("PWD");
  if(cwd)
  {
    return cwd;
  }
  else 
  {
    return "none";
  }
}
int main()
{
  char usercommand[NUM];
  printf("[%s@%s %s]$ ",getUsername(),getHostname(),getCwd());
  scanf("%s",usercommand);
  return 0;
}

打印效果:

2、【输入命令行参数】

但是这样编写的代码会存在一些问题:

如果我们用scanf读取输入的命令行参数,只会读到一个不含空白符的字符串,因为scanf会以空白符为结尾。我们可以看看:

所以我们不能用scanf读取命令行参数,可以用fgets:【fgets - C++ Reference】

注意:

puts遇到空字符停止输出,在输出字符串时会自动在字符串末尾(\0)加一个换行符。
gets()丢弃输入中的换行符,puts()在输出中添加换行符。

fgets()保留输入中的换行符,fputs()不在输出中添加换行符。

所以更改如下:

最后我们对其进行封装:

2.2、【解析命令行参数】

1、【分割命令行参数】

用户输入参数(指令)后,shell会对该行参数进行解析,一般会将字符串按空格进行分割,然后分割好的命令行参数就可以作为程序替换函数exec*的参数,从而实现程序替换,并调用对应的命令程序,而我们这里用strtok函数【strtok - C++ Reference】分割字符串。

这里我们直接进行封装:

最终达到下面的效果即可:

命令行提示符、用户与命令行交互、解析参数的功能实现了,现在我们需要根据用户输入的参数(指令),执行程序。但在执行程序之前,我们必须对指令进行判断。

2、【判断指令类型】

我们之前学习过指令,Linux系统的指令一般可以分为两类 :

  • 一类是内建指令(builtin shell command),内部指令是指内建在shell中的指令,但我们执行该类指令时,不需要额外创建进程,所以内部指令执行的效率高,内建命令就是bash自己执行的,类似于自己内部的一个函数【SHELL编程之内建命令 | Zorro’s Linux Book】
  • 另一类是外部指令(external shell command)。外部指令是指非内建于shell的指令,我们执行该类指令时,会额外创建一个进程。

我们可以通过指令type判断一个指令是否是内建指令,type命令来自英文单词“类型”,其功能是用于查看命令类型,如需区分某个命令是Shell内部指令还是外部命令,则可以使用type命令进行查看。

【参考资料】:type命令 – 查看命令类型 – Linux命令大全(手册)

显示出文件路径的一般是外部命令,显示“is a shell builtin”是内建命令,内建命令不是存放在磁盘中的可执行程序,我们不能简单的创建子进程并进行程序替换,来实现内建命令的调用,对于内建命令我们需要作特殊处理。

3、【外部命令的执行】

对于外部命令的执行,我们不需要考虑太多,只需要在我们的shell进程中创建子进程,使用分割好的命令行参数,进行程序替换,从而完成对应命令的调用。

具体如下:

4、【内建命令的执行】

由于内建命令不同于外部命令只进行简单的程序替换即可完成,内建命令需要特殊的处理,所以对于不同的内建命令我们有不同的处理措施,下面我们看几个例子:

cd命令:

我们知道cd + 目标路径,就可以将我们当前的工作目录改为cd命令后面的目标路径,所以我们可以通过chdir系统调用,将当前的工作路径换为“目标路径”

将工作目录改变以后我们可以通过getcwd函数来进行查看:

需要注意的是,这里我们仍然是创建子进程来实现cd命令所以我们会在子进程中使用chdir函数,而chdir只会进程的当前工作目录,只会影响当前进程及其子进程的工作目录,不会影响环境变量中的PWD,PWD 是由 shell 自动维护,而不是由内核直接管理。

对于典型的 shell,PWD 在你使用命令(如 cd)改变目录时会被更新,但是,直接用系统调用 chdir 不会自动更新 PWD。所以我们仅仅使用chdir函数就会出现下面的场景:

所以当我们调用chdir更改当前工作目录后,还要对环境变量PWD进行修改。先通过调用getcwd()获取当前工作目录的绝对路径,并将其存放到临时空间tmp中,之后通过函数sprintf将tmp的值格式化给全局变量cwd,最后再使用函数putenv将全局变量cwd覆盖环境变量PWD,最后就可以完成修改如下:

export指令:

像cd指令一样,一旦我们识别到对应指令为export,我们就要对其进行特殊的处理,我们知道export命令可用于显示或设置环境变量。所以我们可以使用putenv函数来实现:

echo指令:

当我们使用echo指令打印字符串,都没什么问题,可当我们像下面这样使用时:

按理说这里应该会为我们打印PATH环境变量,但是echo好像将其当成了字符串进行输出,所以我们要来解决一下这个问题,与前面一样我们对其进行针对性特殊处理即可:

5、【实现重定向】

在myshell当中添加重定向功能的步骤大致如下:

  1. 对于获取到的命令进行判断,若命令当中包含重定向符号>>>或是<,则该命令需要进行处理。
  2. 设置type变量,type为0表示命令当中包含输出重定向,type为1表示命令当中包含追加重定向,type为2表示命令当中包含输入重定向。
  3. 重定向符号后面的字段标识为目标文件名,若type值为0,则以写的方式打开目标文件;若type值为1,则以追加的方式打开目标文件;若type值为2,则以读的方式打开目标文件。
  4. 若type值为0或者1,则使用dup2接口实现目标文件与标准输出流的重定向;若type值为2,则使用dup2接口实现目标文件与标准输入流的重定向。

请看下面的实现过程:

最后注意每次进行新指令的输入时把我们定义的redir和filename两个全局变量进行清理:

三、【总结说明以及完整代码】

1、【使shell循环工作】

到这里就算把大部分常见的功能实现完全了,但是细心的你一定会发现,我们的shell还存一个致命的缺陷,就是我们好像只能执行一次命令,所以我们要对其进行完善,是我们的shell能够像正常shell一样,能够连续工作:

2、【代码中的全局变量说明】

我们可以看到我们的代码中用了很多全局变量,那么它们有什么作用呢?

我们先来看lastcode,该全局变量表示程序的退出码,这样我们在父进程等待子进程,或者程序退出的时候获得退出信息:

下面再让我们看看enval和cwd:

我们会发现,这两者都是在调用函数putenv时使用的,也就是说当我们需要添加换将变量时需要定义它们两个定义为全局变量,那么是为什么呢?

首先我们假设没有这两个全局变量,而是将enval和cwd定义在了函数中:

我们要知道当进程启动时,会专门开辟一块空间用来存储命令行参数和环境变量,同时用一个字符串指针数组管理这些环境变量,这个管理环境变量的字符串指针数组就叫做环境变量表(char* envrion[]),当我们用putenv新增一个环境变量时,这时环境变量表会分配一个元素,也就是字符串指针指向这个新增的环境变量,这个新增的环境变量并没有添加到专门存储环境变量的内存空间中(也就是environ指向的空间),而是在栈区,这是因为我们使用argv[1],作为路径path,去当作新传入的环境变量(这里在cd指令中path是目标路径,我们要通过改变环境变量来实现命令行提示符中路径部分的持续变化,而在export中path是我们要导入的环境变量),大致如下:

所以我们通过putenv函数将path添加到环境变量表中,实际上是在environ中创建了一个字符串指针指向了path,而path本身存放在栈区中:

而当我们输入重新在命令行输入指令时,就会刷新命令行参数表argv,如果argv可以分割成两个字符串(也就是argv[0],argv[1]均存在),第二个字符串就会覆盖原来的argv[1],“这样环境变量表environ就找不到原来新增的环境变量了因为被覆盖了。

所以,我们要自己维护一个存放环境变量的空间myenv或cwd,将新增的环境变量存放在myenv和cwd中:

这样就不会覆盖了。 (但再添加一个新的环境变量会覆盖旧的环境变量,大家也可以把自己维护的环境变量设置成二维数组的形式,在堆上申请空间)。

3、【完整代码及效果演示】

为了将系统的shell和我们自己写的shell进行区分,我们可以通过之前写进度条的颜色方案为我们的命令行提示符部分进行上色:

同时也应使用Makefile进行管理:

完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#define NUM 1024   //用来存放输入的数组大小 
#define SEP " "

#define SIZE 64
char cwd[1024];
char enval[1024]; // for test 
int lastcode=0;
//重定向类别
#define NoneRedir   0// 不是重定向
#define OutputRedir 1//输出重定向
#define AppendRedir 2 //追加重定向
#define InputRedir  3//输入重定向
int redir=NoneRedir;//全局变量,redir,用来标识
char* filename=NULL;// 文件名
//获取用户名
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";
  }
}
//获取工作目录
const char* getCwd()
{
  const char* cwd=getenv("PWD");
  if(cwd)
  {
    return cwd;
  }
  else 
  {
    return "none";
  }
}
//获取命令行参数
int getUserCommand(char *command, int num)
{
    printf("\033[35;1m[%s@\033[32;1m%s \033[34;1m%s]\033[31;1m %c\033[0m ", getUsername(), getHostname(), getCwd(),'$');
    char *r = fgets(command, num, stdin); 
    if(r == NULL)
      return -1;
    command[strlen(command) - 1] = '\0';
    return strlen(command);
}
//分割命令行参数
void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while( out[argc++] = strtok(NULL, SEP));
}
//执行外部命令
int execute(char *argv[])
{
    //
    pid_t id = fork();//创建子进程
    if(id < 0) 
      return -1;//创建失败
    else if(id == 0) //child
    {
        // 程序替换会不会影响曾经的重定向呢??不会!! 为什么?如何理解??
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename, O_RDONLY); // 差错处理我们不做了
            dup2(fd, 0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            //do nothing
        }
        //程序替换
        execvp(argv[0], argv); // argv[0]-->ls,argv[1]-->-a,argv[2]-->-l,argv[3]-->NULL 
        exit(1);//子进程结束后使用exit函数退出
    }
    else // father
    {
      //父进程等待子进程
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

void cd(const char *path)
{

   char tmp[1024];//定义临时空间存放当前工作目录
   getcwd(tmp, sizeof(tmp));//获取当前工作目录
   printf("当前工作目录:%s\n",tmp);
   printf("环境变量:%s\n",getenv("PWD"));
   chdir(path);//改变工作目录
   getcwd(tmp, sizeof(tmp));//更新工作目录,并将其存放到tmp
   sprintf(cwd, "PWD=%s", tmp); //使用tmp覆盖环境变量,cwd的值被格式化为"PWD=tmp"
   putenv(cwd);//覆盖PWD环境变量
   printf("当前工作目录:%s\n",tmp);
   printf("环境变量:%s\n",getenv("PWD"));
}


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

extern  char    **environ;  //使用全局变量environ打印所有环境变量
//内建命令
int doBuildin(char *argv[])
{
    if(strcmp(argv[0], "cd") == 0)//判断是否是cd命令
    {
        char *path = NULL;
        if(argv[1] == NULL)
          path=homepath();//cd后为空就设置为家目录
        else
          path = argv[1];//不为空就设置为目标路径
        cd(path);//执行cd逻辑
        return 1;
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) 
          return 1;//若未传入环境变量则返回
        strcpy(enval, argv[1]);//否则将传入的环境变量拷贝到全局变量enval中
        putenv(enval); // 使用putenv函数将enval添加到环境变量中
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        if(argv[1] == NULL)
        {
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) > 1)//判断是否是类似与$PATH,$?这种参数
        { 
            char *val = argv[1]+1; // 对于不同的参数进行不同的处理,$?就要打印退出码
            if(strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else//其他的使用getenv函数进行获取并打印即可
            {
                const char *enval = getenv(val);
                if(enval) 
                  printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else//不是$*这种参数就当作字符串即可 
        {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    else if(0)
    {
      //有其他内建指令,再进行特殊处理
    }
    return 0;
}
#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; }while(0)//这是一个宏函数,作用是跳过空格,里面使用了函数isspace,该函数作用是判断pos位置是否为空格
void checkRedir(char usercommand[], int len)
{
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    char *end = usercommand + len - 1;//从后遍历
    char *start = usercommand;
    while(end>start)
    {
        if(*end == '>')//可能是输入重定向,也可能是追加重定向
        {
            if(*(end-1) == '>')//输入重定向
            {
                *(end-1) = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = AppendRedir;
                break;
            }
            else//追加重定向
            {
                *end = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')//输出重定向
        {
            *end = '\0';
            filename = end+1;
            SkipSpace(filename); // 如果有空格,就跳过
            redir = InputRedir;
            break;
        }
        else//说明未找到“<,<<,>”三个中的一个,继续向前遍历
        {
            end--;
        }
    }
}
int main()
{
 
  while(1)
  {
     redir=NoneRedir;
     filename=NULL;
     char usercommand[NUM];
     char *argv[SIZE];
      //获取
     int n = getUserCommand(usercommand, sizeof(usercommand));
     if(n<=0)//查看getUserCommand函数返回值,n<=0说明其输入的是空串
     {
       continue;//空串直接跳过循环,重新输入
     }
     //获得字符串以后,首先检查是否进行了重定向
     //ls -a -l > log.txt ?> "ls -a -l" [redir_type]   "log.txt"
     checkRedir(usercommand,sizeof(usercommand));
     //分割字符串
     commandSplit(usercommand, argv);
     n = doBuildin(argv);
     if(n)
     {
       continue;//n不为0说明为内建命令,就不向下继续执行了。
     }
     n= execute(argv);

  }
  return 0;
}

演示效果:

总结


到这里我们就完了一个简易shell的实现,其中我们我们使用了,进程替换,字符分割,重定向,以及文件的打开及关闭,甚至包括dup2函数,等等知识,本篇博客到这里也就结束了,希望对你有所帮助!

......................................................................是否沉默就是你的回答,我们都别挣扎,去爱他

                                                                                                          ————《爱我还是他》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2250965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【超全总结】深度学习分割模型的损失函数类别及应用场景

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

新增工作台模块,任务中心支持一键重跑,MeterSphere开源持续测试工具v3.5版本发布

2024年11月28日&#xff0c;MeterSphere开源持续测试工具正式发布v3.5版本。 在这一版本中&#xff0c;MeterSphere新增工作台模块&#xff0c;工作台可以统一汇总系统数据&#xff0c;提升测试数据的可视化程度并增强对数据的分析能力&#xff0c;为管理者提供测试工作的全局…

大模型训练核心技术RLHF

本文此次的主要内容是使用强化学习训练语言模型的过程&#xff0c;特别是通过人类反馈的强化学习&#xff08;RLHF&#xff09;技术来微调大语言模型。本文先介绍了预训练模型的使用&#xff0c;然后重点介绍了RLHF的第二阶段&#xff0c;即将下游任务以特定数据集的形式交给大…

Python学习笔记之IP监控及告警

一、需求说明 作为一名运维工程师&#xff0c;监控系统必不可少。不过我们的监控系统往往都是部署在内网的&#xff0c;如果互联网出口故障&#xff0c;监控系统即使发现了问题&#xff0c;也会告警不出来&#xff0c;这个时候我们就需要补充监控措施&#xff0c;增加从外到内的…

联想YOGA Pro 14s至尊版电脑找不到独立显卡(N卡)问题,也无法安装驱动的问题

问题描述 电脑是联想YOGA Pro 14s至尊版&#xff0c;电脑上装的独立显卡是4060&#xff0c;一直是能够使用独立显卡的。然而有两次突然就找不到显卡了&#xff0c;NVIDIA CONTROL PANEL也消失了&#xff0c;而且也无法安装驱动。具体表现如下&#xff1a; 无法连接外接显示器…

【优先算法-滑动窗口——包含不超过两种字符的最长子串】

目录 1.题目解析 题目来源 测试用例 2.算法原理 1.入窗口 2.出窗口 3.更新结果 3.实战代码 代码解析 1.题目解析 题目来源 包含不超过两种字符的最长子串——牛客网 测试用例 2.算法原理 1.入窗口 这里的窗口限制条件为:窗口内不能超过两种字符&#xff0c;所以使用…

图片预处理技术介绍4——降噪

图片预处理 大家好&#xff0c;我是阿赵。   这一篇将两种基础的降噪算法。   之前介绍过均值模糊和高斯模糊。如果从降噪的角度来说&#xff0c;模糊算法也算是降噪的一类&#xff0c;所以之前介绍的两种模糊可以称呼为均值降噪和高斯降噪。不过模糊算法对原来的图像特征的…

Python蒙特卡罗MCMC:优化Metropolis-Hastings采样策略Fisher矩阵计算参数推断应用—模拟与真实数据...

全文链接&#xff1a;https://tecdat.cn/?p38397 本文介绍了其在过去几年中的最新开发成果&#xff0c;特别阐述了两种有助于提升 Metropolis - Hastings 采样性能的新要素&#xff1a;跳跃因子的自适应算法以及逆 Fisher 矩阵的计算&#xff0c;该逆 Fisher 矩阵可用作提议密…

cad软件打不开报错cad acbrandres dll加载失败

一切本来很顺利哒 但是&#xff0c;当我用快捷方式打开时&#xff0c;就出现了这个错误。进入文件路径&#xff0c;是有这个的&#xff1b; 在文件路径直接打开&#xff0c;也会提示错误 原因竟然是我改了个名字&#xff1a; 随便选的文件路径&#xff0c;空的,文件名为Acr…

HBU深度学习作业9

1. 实现SRN &#xff08;1&#xff09;使用Numpy实现SRN import numpy as npinputs np.array([[1., 1.],[1., 1.],[2., 2.]]) # 初始化输入序列 print(inputs is , inputs)state_t np.zeros(2, ) # 初始化存储器 print(state_t is , state_t)w1, w2, w3, w4, w5, w6, w7, …

泛化调用 :在没有接口的情况下进行RPC调用

什么是泛化调用&#xff1f; 在RPC调用的过程中&#xff0c;调用端向服务端发起请求&#xff0c;首先要通过动态代理&#xff0c;动态代理可以屏蔽RPC处理流程&#xff0c;使得发起远程调用就像调用本地一样。 RPC调用本质&#xff1a;调用端向服务端发送一条请求消息&#x…

纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架

前言​ 开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C 库&#xff0c;如 ​​OpenCV​​ 或 ​​​dlib​​​&#xff0c;但通过 ​​​cgo​​​ 调用 C 程序会引入巨大的延迟&#xff0c;并在性能方面产生显著的权衡。…

基于SpringBoot实现的编程训练系统(代码+论文)

&#x1f389;博主介绍&#xff1a;Java领域优质创作者&#xff0c;阿里云博客专家&#xff0c;计算机毕设实战导师。专注Java项目实战、毕设定制/协助 &#x1f4e2;主要服务内容&#xff1a;选题定题、开题报告、任务书、程序开发、项目定制、论文辅导 &#x1f496;精彩专栏…

【Spring】Spring IOCDI:架构旋律中的“依赖交响”与“控制华章”

前言 &#x1f31f;&#x1f31f;本期讲解关于Spring IOC&DI的详细介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么…

webpack(react)基本构建

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它的主要功能是将各种资源&#xff08;如 JavaScript、CSS、图片等&#xff09;视为模块&#xff0c;并将它们打包成一个或多个输出文件&#xff0c;以便…

mysql--二进制安装编译安装yum安装

二进制安装 创建用户和组 [rootlocalhost ~]# groupadd -r -g 306 mysql [rootlocalhost ~]# useradd -r -g 306 -u 306 -d /data/mysql mysql 创建文件夹并添加所属文件用户和组 [rootlocalhost ~]# mkdir -p /data/mysql [rootlocalhost ~]# chown mysql:mysql /data/mysql …

DRM(数字权限管理技术)防截屏录屏----ffmpeg安装

提示&#xff1a;ffmpeg安装 文章目录 [TOC](文章目录) 前言一、下载二、配置环境变量三、运行ffmpeg四、文档总结 前言 FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的…

MongoDB集群分片安装部署手册

文章目录 一、集群规划1.1 集群安装规划1.2 端口规划1.3 目录创建 二、mongodb安装&#xff08;三台均需要操作&#xff09;2.1 下载、解压2.2 配置环境变量 三、mongodb组件配置3.1 配置config server的副本集3.1.1 config配置文件3.1.2 config server启动3.1.3 初始化config …

小程序-基于java+SpringBoot+Vue的乡村研学旅行平台设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

Ubuntu 包管理

APT&dpkg 查看已安装包 查看所有已经安装的包 dpkg -l 查找包 apt search <package_name>搜索软件包列表&#xff0c;找到与搜索关键字匹配的包 dpkg与grep结合查找特定的包 dpkg -s <package>&#xff1a;查看某个安装包的详细信息 安装包 apt安装命令 更新…