C语言编写Linux的Shell外壳

目录

一、输出命令行

1.1 了解环境变量

1.2 获取用户名、主机名、当前路径

1.3 缓冲区改进MakeCommandLine

二、获取用户命令

2.1 读取函数的选择

2.2 细节优化

2.3 返回值

三、指令和选项分割

3.1 strtok 函数

3.2 分割实现 

四、执行命令

4.1 fork 方法

4.2 进程等待

4.3 进程替换

4.4 程序编写

五、程序优化

5.1 执行次数

5.2 检测命令是否为内建命令

5.3 子进程执行失败

5.4 命令行路径更改

六、完整代码


在学习之前我们要认识到,Shell外壳中的命令行以及我们输入的指令都是字符串!

首先我们要创建两个文件:MyShell.cmakefile ,一个存储我们的 Shell 外壳,一个方便操作

其次,我们看一下 MyShell.c 中需要包含的头文件:

  1 #include <stdio.h>
  2 #include <unistd.h>     //进程创建接口
  3 #include <sys/types.h>  //进程等待
  4 #include <sys/wait.h>   //进程等待     

一、输出命令行


首先我们先来认识一下命令行,其中, Flash 是当前用户名、@ 后紧跟的是当前主机名、主机名空格后紧跟的是当前路径。所以由此我们知道,如果想打印出我们的命令行,我们至少要知道三个信息:1.用户名 2.主机名 3.当前路径

1.1 了解环境变量

这三个信息如何拿去呢?我们的操作系统中有自带的环境变量,在环境变量中存放着我们需要的三个信息。我们如何查我们的环境变量呢?

有三种方法:

1.命令行参数 -> [env] ,当我们在命令行输入指令,系统就会显示出一大串信息(这里有省略),其中就有我们需要的 [USER] [PWD] [HOSTNAME]

2.ENVIRON 等第三方提供的接口,这里不做详细介绍

3.使用C语言提供的接口函数,如 getenv() ,我们这里使用该方式,下面会详细介绍。

1.2 获取用户名、主机名、当前路径

getenv 函数 —— stdlib.h C语言标准库

其中,传参 [const char* name] 表示的是我们需要从环境变量中拿取的变量名称,我们这里需要拿到的就是上述使用命令行参数 [env] 读取到的 [USER] [HOSTNAME] [PWD],它的返回值就是我们需要的一个字符串。

现在我们就可以来编写我们的函数了。

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  printf("[%s@%s %s]>\n", UserName, HostName, Cwd);
}
int main()
{
  MakeCommandLine();
  return 0;
}

我们还可以继续改进一下,使用缓冲区的概念

1.3 缓冲区改进MakeCommandLine

首先我们先宏定义一个缓冲区的大小,这里我设置为 256 。

#define SIZE 256
char Line[SIZE];//自定义缓冲区

其次我们再来学习一个函数 snprintf

按照参数的顺序,依次是 s : 需要写入的目的地 n : 写入字符的数量 format : 标准格式

使用 snprintf 就可以像我们自定义的缓冲区里写啦!

snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
printf("%s\n", Line);

成品: 

#define SIZE 256
//只有MakeCommandLine函数变化,只展示该函数
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s\n", Line);
}

但是还有一个细节,当我们想让程序慢一点结束时,使用 sleep 函数,就会发生神奇的一幕:

为什么没有立刻打印呢?原因是 stdout 的缓冲问题。printf 是行缓冲的,通常情况下,当遇到换行符('\n')时,缓冲区的内容会被送往 stdout 进行输出。然而在某些情况下,如果在调用 sleep 之前没有刷新缓冲区,那么输出可能会延迟直到缓冲区被刷新。

我们的解决方法是使用 fflush 函数!这将刷新(即清空并发送)包含 printf 输出的缓冲区,从而确保立即看到输出:

snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
printf("%s\n", Line);
fflush(stdout);
sleep(5);

二、获取用户命令

首先,我们在输出命令行时,为了方便阅读,用 printf 输出 Line 时加了 '\n' ,我们现在要保证命令行和命令在同一行,所以现在我们要删除掉 '\n' 。

2.1 读取函数的选择

我们可以继续使用 scanf 函数来读取命令吗?按照我们输入的命令,如 [ls -a -l] ,他们都是以空格为分隔符,显然与我们的 sacnf 发生了冲突,而且无法控制每次输入命令的空格数量,所以我们不能使用 scanf 读取输入。

我们的命令都是用行读取,C语言也提供了这样的函数 fgets 。

其中,返回指向读取到字符开头的指针,传参依次为读取字符后存放的位置,读取字符的长度,读取字符的位置。 

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;                                                    
 }     
 int main()                                                         
 {                                                                  
    //输出命令行                                                     
    MakeCommandLine();                                               
    //读取用户命令                                                   
    char UserCommand[SIZE];                                          
    GetUserCommand(UserCommand, sizeof(UserCommand));  
    printf("echo : %s\n", UserCommand); //打印验证一下是否被读取                                            
    return 0;
 }

2.2 细节优化

现在又来了一个细节问题:

fgets 按行读取,如果我们想执行就必须按 [回车] ,假设我们输入的是"Hello World",那么其读取到的就是"Hello World\n",再加上我们自己写的 printf 中的 '\n' 就变成了两行,下面我们进一步优化一下,只需要把命令的最后一个字符改成 '\0' 即可:

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;
    Command[strlen(Command) - 1] = '\0';                                                    
 } 

2.3 返回值

关于函数的返回值,为了与 [return -1] 区分开,也为了更好地执行命令,我们可以返回一下读取到的命令长度,这样当命令 [> 0] 时,我们再继续执行,否则直接退出:

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;
    Command[strlen(Command) - 1] = '\0';    
    return strlen(Command);                                                
 } 
 int main()                                                         
 {                                                                  
    //输出命令行                                                     
    MakeCommandLine();                                               
    //读取用户命令                                                   
    char UserCommand[SIZE];                                          
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));  
    (void)n;//暂时不搞,先强转一下,防止警告                                         
    return 0;
 }

三、指令和选项分割

像我们上面使用的 []ls -a -l] ,它们是由指令和选项构成,所以我们如果要执行,肯定也要把读取用户传入的字符串分割成下面的形式, [ls] [-a] [-l] 。

3.1 strtok 函数

那么如何分割呢?这又要使用到C语言中的字符串函数 strtok 。

其中,返回值是分割后的小字符串,传入参数 str 是要分割的字符串, delimiters 是分隔符

如果首次分割是已经传入参数 str ,那么再次调用时把 str 设置为 NULL ,strtok就会默认继续分割上次的字符串。

一说到分隔符,我们这里的分隔符显而易见的就是空格啦!由于我们的分割是把一个字符串分割成若干小字符串,所以我们就可以直接定义一个全局的数组,用来挨个存放这些小字符串:

但是要注意, strtok 函数使用的分隔符都是字符串,我们的空格不能设置为 ' ' ,而应该为 " "

 #define SEP " "    

3.2 分割实现 

void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));//strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}

我们还可以通过打印来验证是否正确:

 int i = 0;                                                               
 for (; argv[i]; i++)
 {
     printf("%s ", argv[i]);
 }
 printf("\n");

四、执行命令

我们之前讲过 Bash 会创建子进程,为了保证安全,都是子进程在执行我们的命令,而我们今天要实现的也是使用子进程来帮助我们执行命令。

4.1 fork 方法

fork 函数并不是我们的C语言函数,而是操作系统提供的,所以我们可以用 man 2 fork 查询

这里不做过多说明,实在不知道可以移步到Linux进程概念(1)-CSDN博客

如果执行的是子进程,那么返回值为0,如果是父进程,会返回子进程的 pid

4.2 进程等待

我们之前说过,如果父进程先于子进程退出,子进程就会变成僵尸进程,为了避免这种影响,父进程可以通过进程等待的方式,回收子进程资源,获取子进程退出信息。

下面我们来介绍几个进程等待的方法:

wait 方法:

    返回值:等待成功返回被等待子进程的 pid ,失败返回 -1

    参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid 方法:

    返回值:

       当正常返回的时候 waitpid 返回收集到的子进程的进程ID;如果设置了选项 WNOHANG,而调用中 waitpid 发现没有已退出的子进程可收集,则返回 0;如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在;

    参数:

    pid:
       pid=-1,等待任一个子进程。与wait等效。
       pid>0.等待其进程ID与pid相等的子进程。
    status:
       WIFEXITED: 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
       WEXITSTATUS: 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    options:
       WNOHANG: 若 pid 指定的子进程没有结束,则 waitpid() 函数返回0,不予以等待。

       若正常结束,则返回该子进程的ID。

4.3 进程替换

我们用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

有六种以 exec 开头的函数,统称为 exec 函数:

#include <unistd.h>

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[]);

这些函数都可以用 man execl 查询:

4.4 程序编写

void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    waitpid(id, &status, 0);
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }
}

下面我们来看一下完整代码以及效果图:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>     //进程创建接口
#include <sys/types.h>  //进程等待
#include <sys/wait.h>   //进程等待

#define SIZE 256
#define NUM 16
#define SEP " "

char* argv[NUM];

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}
int GetUserCommand(char Command[], size_t n)
{
  char* s = fgets(Command, n, stdin);
  if (!s) return -1;
  Command[strlen(Command) - 1] = '\0';
  return strlen(Command);
}
void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));
      //strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}
void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    waitpid(id, &status, 0);
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }

}
int main()
{
  //输出命令行
  MakeCommandLine();
  //读取用户命令
  char UserCommand[SIZE];
  int n = GetUserCommand(UserCommand, sizeof(UserCommand));
  (void)n;
  printf("echo : %s\n", UserCommand);
  //指令和选项分割
  SplitCommand(UserCommand, sizeof(UserCommand));
  //执行命令
  ExecuteCommand(UserCommand, sizeof(UserCommand));
  return 0;
}

五、程序优化

5.1 执行次数

虽然我们的 Shell 已经完成的有一点雏形了,但是怎么这个外壳只能使用一次呀?我们是不是要让他多执行几次呢?所以我们就要把这几个步骤都放到一个 while 循环中。

int main()
{
  while(1)
  {
    //输出命令行
    MakeCommandLine();
    //读取用户命令
    char UserCommand[SIZE];
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));
    (void)n;
    //指令和选项分割
    SplitCommand(UserCommand, sizeof(UserCommand));
    //执行命令
    ExecuteCommand(UserCommand, sizeof(UserCommand));
   }
  return 0;
}

5.2 检测命令是否为内建命令

我们在我们的 Shell 中使用 cd 命令,但是我们的命令行无法进入某目录,这是为什么呢?
因为我们上面创建了子进程,我们的 cd 命令是让子进程执行的,和我们真正的 bash 没有关系,我们正确的做法是让父进程执行!

什么是内建命令呢?

下面我们来看看如何检测是否为内建命令。

虽然我们的 [cd] 已经可以使用,但是我们的命令行路径怎么不回退呢?
因为我们还要更改我们的环境变量!

更改环境变量要用到系统调用,需要获取当前路径,并写入环境变量中。

获取当前路径:

man 2 getcwd

写入环境变量:

man snprintf

man putenv

所以我们此时在对我们的 cd 函数做修改:

void ExecuteCd()
{
  const char* path = argv[1];
  if (path == NULL) path = GetHome();
  else chdir(path);

  char tmp[SIZE];
  getcwd(tmp, sizeof(tmp));
  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}

当然还有其他内建命令,方法都诸如此类。

5.3 子进程执行失败

我们直接在全局定义一个退出码 int lastcode ,然后在父进程中左对应的修改:

void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if (lastcode != 0) printf("%s:%s:%d\n", argv[0], strerror(lastcode), lastcode);
    }
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }
}

5.4 命令行路径更改

在我们的 XShell 中提供的 Shell 外壳,其命令行的路径都是相对路径,我们的 Shell 也可以改成这样,如下:

我们这里采用了宏函数,也可以使用正常函数,在使用宏函数是时,若程序是代码块,建议放在 [do while(0)] 中,如下:

#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)

同时在输出命令行中也调用该函数,如下:

void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  
  SkipPath(Cwd);
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}

六、完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>     //进程创建接口
#include <sys/types.h>  //进程等待
#include <sys/wait.h>   //进程等待

#define SIZE 256
#define NUM 16
#define SEP " "
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)

char* argv[NUM];
char cwd[SIZE];
int lastcode = 0;

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  
  SkipPath(Cwd);
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}
int GetUserCommand(char Command[], size_t n)
{
  char* s = fgets(Command, n, stdin);
  if (!s) return -1;
  Command[strlen(Command) - 1] = '\0';
  return strlen(Command);
}
void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));
      //strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}
const char* GetHome()
{
  const char* home = getenv("HOME");
  if (home == NULL) return "/";
  return home;
}
void ExecuteCd()
{
  const char* path = argv[1];
  if (path == NULL) path = GetHome();
  else chdir(path);

  char tmp[SIZE];
  getcwd(tmp, sizeof(tmp));
  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}
int CheckBuiltin()
{
  int ch = 0;//默认非内建命令
  const char* command = argv[0];
  if (strcmp(command, "cd") == 0)
  {
    ch = 1;
    ExecuteCd();
  }
  return ch;
}
void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if (lastcode != 0) printf("%s:%s:%d\n", argv[0], strerror(lastcode), lastcode);
    }
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }

}

int main()
{
  while(1)
  {
    //输出命令行
    MakeCommandLine();
    //读取用户命令
    char UserCommand[SIZE];
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));
    (void)n;
    //指令和选项分割
    SplitCommand(UserCommand, sizeof(UserCommand));
    //检查是否为内建命令
    int ch = CheckBuiltin();
    if (ch) continue;
    //执行命令
    ExecuteCommand(UserCommand, sizeof(UserCommand));
   }
  return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt C++ | Qt 元对象系统、信号和槽及事件(第一集)

01 元对象系统 一、元对象系统基本概念 1、Qt 的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。 2、元对象系统是 Qt 对原有的 C++进行的一些扩展,主要是为实现信号和槽机制而引入的, 信号和槽机制是 Qt 的核心特征。 3、要使用元…

蓝桥杯嵌入式学习笔记(9):RTC程序设计

目录 前言 1. RTC介绍 2. 使用CubeMx进行源工程配置 3. 代码编程 3.1 准备工作 3.2 进行bsp_rtc.h编写 3.3 进行bsp_rtc.c编写 3.4 main.c编写 3.4.1 头文件引用 3.4.2 变量声明 3.4.3 子函数声明 3.4.4 函数实现 3.4.5 main函数编写 4. 代码实验 5. 总结 前言 因本人备赛蓝…

2024年购买阿里云服务器多少钱?100元-5000元预算

2024年阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核4G服务…

IntelliJ IDEA中文---强化智能编码与重构,提升开发效率

IntelliJ IDEA 2023是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为Java开发人员设计。它支持智能代码编辑、自动补全和重构&#xff0c;帮助开发者提高编码效率。同时&#xff0c;内置了丰富的调试工具&#xff0c;支持断点调试和变量监视&#xff…

STM32-04基于HAL库(CubeMX+MDK+Proteus)中断案例(按键中断扫描)

文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式&#xff0c;生成代码四、MDK打开生成项目&#xff0c;编写HAL库的按键检测代码五、运行仿真程序&#xff0c;调试代码 一、功能需求分析 在完成GPIO输入输出案例之后&#xff0c;开始新的功能…

2024阿里云老用户服务器优惠价格99元和199元

阿里云服务器租用价格表2024年最新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核…

[RK356X_LINUX] 关于UMS功能电脑不显示盘符

问题描述 根据356x_linux\docs\Linux\ApplicationNote\Rockchip_Quick_Start_Linux_USB_Gadget_CN.pdf文档执行命令配置UMS功能。 虽然电脑端显示有UMS设备图标&#xff0c;但无盘符显示。 在执行/etc/init.d/S50usbdevice restart后会出现打印&#xff1a; Starting /usr/b…

MySQL数据库 数据库基本操作(二):表的增删查改(上)

1. CRUD CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写,就是数据库基本操作中针对表的一系列操作. 2. 新增(create) -->insert 语法: insert into 表名 [列名1,列名2…] values (val1,val2…) [注意] 列名可以没有,如果没有列名…

Delphi 是一种内存安全的语言吗?

上个月&#xff0c;美国政府发布了 "回到基石 "报告&#xff1a; 通往安全和可衡量软件之路 "的报告。该报告是美国网络安全战略的一部分&#xff0c;重点关注多个领域&#xff0c;包括内存安全漏洞和质量指标。 许多在线杂志都对这份报告进行了评论&#xff0…

跑mmdec(自用)

1. 准备工作 进服务器先conda activate openmmlab 如果没有conda初始化的话&#xff0c;要先 source ~/miniconda3/bin/activate 然后cd mmdetection进目录 2. 数据处理 把数据变成这样的格式

时序预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络时间序列预测

时序预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络时间序列预测 目录 时序预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CPO-BiLST…

C++ //练习 11.12 编写程序,读入string和int的序列,将每个string和int存入一个pair中,pair保存在一个vector中。

C Primer&#xff08;第5版&#xff09; 练习 11.12 练习 11.12 编写程序&#xff0c;读入string和int的序列&#xff0c;将每个string和int存入一个pair中&#xff0c;pair保存在一个vector中。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#x…

设备巡检电力水利物业巡检小程序开源版开发

设备巡检电力水利物业小程序开源版&#xff0c;旨在提供一个全面而灵活的解决方案&#xff0c;以下是其主要功能概览&#xff1a; 用户身份认证&#xff1a;用户能够凭借手机号等便捷方式登录或注册账号。 主页概览&#xff1a;小程序主页呈现巡检系统的基础信息与操作指引&…

vue3+elementPlus:实现数字滚动效果(用于大屏可视化)

自行封装注册一个公共组件 案例一&#xff1a; //成功案例&#xff1a; //NumberScroll.vue /* 数字滚动特效组件 NumberScroll */<template><span class"number-scroll-grow"><spanref"numberScroll":data-time"time"class&qu…

最新408试卷分析+备考经验分享

408出题再糟糕&#xff0c;你是不是还是要考&#xff1f; 别管出题人出多刁钻的题&#xff0c;大家拿到的卷子都是一样的&#xff0c;要难就都难&#xff0c;要刁钻就一起g... 所以再潜心钻研出题规律或出题套路&#xff0c;不如多花些时间去多复习巩固几遍知识点&#xff01…

JAVAEE之IoCDI

Spring 是⼀个 IoC&#xff08;控制反转&#xff09;容器&#xff0c;作为容器, 那么它就具备两个最基础的功能&#xff1a; • 存 • 取 Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建…

redis集合Set

set是一种无序集合。它和列表的区别在于列表中的元素都是可以重复的&#xff0c;而set中的元素是不能重复的。而且set中的元素&#xff0c;并不像列表那样是具有顺序的。 SADD是添加一个元素。course是集合。 SMEMBERS SISMEMBER判断Redis在不在集合course里 SREM是用来删除Re…

Celery的任务流

Celery的任务流 在之前调用任务的时候只是使用delay()和apply_async()方法。但是有时我们并不想简单的执行单个异步任务&#xff0c;比如说需要将某个异步任务的结果作为另一个异步任务的参数或者需要将多个异步任务并行执行&#xff0c;返回一组返回值&#xff0c;为了实现此…

Flutter中setState函数的使用注意事项

文章目录 Flutter中setState函数的使用注意事项只能在具有State对象的类中使用不要在build方法中使用将状态更新逻辑放在setState方法内部避免频繁调用使用回调函数更新状态 Flutter中setState函数的使用注意事项 setState()函数是Flutter中非常重要的一个函数&#xff0c;它用…

【教程】宝塔default.db占用空间几十g解决方法|宝塔占用磁盘空间特别大解决方法|宝塔磁盘被占满怎么清理

目录 一、前言二、排查问题三、解决方法 一、前言 用过宝塔创建网站&#xff0c;大家应该都非常熟悉&#xff0c;但是用随着用的时间越来越多&#xff0c;宝塔所占用的空间也越来越多&#xff0c;不停的加大数据盘都没有用&#xff0c;我原先买了30G够用了&#xff0c;随着时间…