自定义 shell文件系统


🏷️ 材料准备

创建一个文件:myshell.c:

#include <stdio.h>

int main()
{

    return 0;
}

创建一个 Makefile 文件,文件内容如下:

  1 mybash:myshell.c

  2   g++ -o $@ $^ -std=c++11                                                   

  3 .PHONY:clean             

  4 clean:           

  5   rm -f mybash

🏷️ 打印提示符,获取用户命令字符串

📌 (打印提示符)填写:用户名,主机名,当前所在的工作目录

我们打开终端,我们会看到这样的东西:
在这里插入图片描述

所以我们自己定义的 shell 文件系统也要获取这些东西,我们可以写一些函数:

// 获取当前的用户名
const char* getUsername()
{}

// 获取当前的主机名
const char* getHostname()
{}

// 获取当前的工作目录
const char* getCwd()
{}

使用getenv() 这个函数可以获取当前的环境变量,头文件是stdlib.h ,所以我们可以如下来写:

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

// 获取当前的用户名
const char *getUsername()
{
    const char *name = getenv("USER");
    if (name)
        return name; // 获取成功就返回名字
    else
        return "none"; // 获取失败就返回 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()
{
    printf("[%s@%s %s]¥\n", getUsername(), getHostname(), getCwd()); // 这里的 printf 输出是仿造终端那种格式的,我把 $ 换成了人民币的 ¥,你可以自己换成其他的
    return 0;
}

make 之后 ,运行一下:

在这里插入图片描述
但是我们发现一个问题,我们运行之后程序就结束了,按理说正常的 shell 是可以输入命令的,我们也想输入命令,所以我们改一下我们的代码:

我们加入以下命令:

#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
 char usercommand[NUM]; // 用来存储用户输入的命令
scanf("%s", usercommand); // 用户输入命令

合在一起是这样的:

#include <stdio.h>
#include <stdlib.h>
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 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;
}

在这里插入图片描述

📌 (获取用户命令字符串)向我们自定义的 bash 中输入命令

接着,我们可以用 echo 来打印一下我们输入的命令是怎么样的

printf("echo:\n%s", usercommand);  // 我们用这条语句来显示一下输入的命令到底是怎么样的

在上面的代码中加上这一行:

#include <stdio.h>
#include <stdlib.h>
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 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); // 用户输入命令
    /******************************************************************************/
    printf("echo:\n%s", usercommand);  // 我们用这条语句来显示一下输入的命令到底是怎么样的
    /******************************************************************************/
    return 0;
}

在这里插入图片描述

因为,我们是用的scanf ,它遇到空格就会停下来。所以我们要换一个,使用 fgets() 函数,头文件是:stdio.h;

#include <stdio.h>
char *fgets(char *str, int num, FILE *stream);

参数:

  • str:指向字符数组的指针,用于存储从文件流中读取的字符串。
  • num:指定最多读取的字符数,包括最后的空字符(\0)。
  • stream:指向 FILE 结构的指针,指定了要读取的文件流。

返回值:

  • 如果成功读取,fgets 返回一个指向 str 的指针。(获取成功就是你输入的字符串的起始地址)
  • 如果遇到错误或文件结束符(EOF),返回 NULL。(获取失败就返回 NULL)

🧲 知识补充:
C 语言会默认打开三个输入输出流,分别叫做:stdin, stdout, stderr; 它们的类型都是 FILE*
stdin : 键盘
stdout : 显示器
stderr : 显示器

有了上面的知识,所以我们将scanf() 函数修改一下,修改成这样:

 fgets(usercommand, sizeof usercommand, stdin);
  1. sizeof usercommand:用于计算 usercommand 数组的大小(即数组中元素的数量,通常以字节为单位)。在这里,它用来确定 fgets 函数可以读取的最大字符数。注意,sizeof 后面紧跟变量名,不需要括号。
  2. stdin:是 C 语言标准库提供的一个全局变量,代表标准输入流。它通常与键盘输入关联。stdinFILE 类型的指针,指向标准输入设备。

关于 sizeof 操作符的使用,它是一个编译时运算符,用于获取变量或类型所占用的字节数。它返回的是一个常量值,所以不需要括号。括号通常用于 sizeof 操作符的语法是当它用于数据类型本身,而不是变量的时候,例如 sizeof(int)

这个时候,我们的代码是这样的:

#include <stdio.h>
#include <stdlib.h>
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 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());
    char *r = fgets(usercommand, sizeof usercommand, stdin);
    if (r == NULL)
        return 1;                     // 获取失败
    printf("%s\n", usercommand); // 我们用这条语句来显示一下输入的命令到底是怎么样的
    return 0;
}

运行之后这里有一个小的细节:

在这里插入图片描述

为了避免这样的情况,我们可以再代码中加入下面这样一行👇🏻:

#include <string.h>

usercommand[strlen(usercommand) - 1] = '\0'; // 将倒数第二个字符变成‘\0’,这样就不会读取到我们最后的那个回车换行了

我们现在的代码:

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

#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 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());
    char *r = fgets(usercommand, sizeof usercommand, stdin);
    if (r == NULL)
        return 1;   // 获取失败

    usercommand[strlen(usercommand) - 1] = '\0';
    printf("%s\n", usercommand); // 我们用这条语句来显示一下输入的命令到底是怎么样的
    return 0;
}

问一下:我们这样写会不会越界呢?

usercommand[strlen(usercommand) - 1] = '\0';

万一usercommand 用 strlen 求长度之后是 0 , 那再减 1 不就越界了吗?
答案是:不会越界。
因为:你的usercommand 的长度不可能是 0 ,因为你就算什么也不输入,最后也要输入个 回车 来运行这个命令,所以usercommand 中肯定有一个回车的。长度就不可能是 0 。

我们把上面的代码封装一下:

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

#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 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 getUserCommand(char *command, int num) // 获取用户输入的命令
{

    printf("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

int main()
{
    char usercommand[NUM]; // 用来存储用户输入的命令

    getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令
    printf("%s", usercommand);                            
    return 0;
}

🏷️ 分割用户输入的字符串

❓什么意思呢?
假设用户输入的是:ls -a -l ,我们要把这个字符串拆解为:“ls”, "-a" , "-l"

创建数组:

我们要把拆分之后的这些字符串存起来,所以我们要创建一个数组:

#define SIZE 64
char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

并且我们希望这个数组以 NULL 结尾。

如何分割这个字符串呢?:

函数:strtok

使用函数:strtok()。头文件:string.h

char *strtok(char *str, const char *delim);

参数

  • str:指向要分割的字符串的指针。在第一次调用 strtok 时,这个参数必须提供要分割的字符串;在随后的调用中,应该传递 NULL
  • delim:指向分隔符集的指针,指定了用于分割字符串的一组字符。

返回值

  • strtok 返回一个指向下一个标记的指针。如果没有更多的标记或遇到字符串结束,返回 NULL
为了使用这个函数,我们要定义一个分隔符来传给它
#define SEP " " // 注意我们这里引号里的是空格
使用函数
    argv[argc++] = strtok(usercommand, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (argv[argc++] = strtop(NULL, SEP)); // 第二次传参和之后的传参就只能传:NULL

让我们逐步分析这段代码:

  1. argv[argc++] = strtok(usercommand, SEP);

    • strtok(usercommand, SEP):调用 strtok 函数,将 usercommand 字符串按照 SEP 中定义的分隔符进行分割,并返回第一个标记。
    • argv[argc++]:将分割得到的第一个标记赋值给 argv 数组的当前位置,并且将 argc加 1。
  2. while (argv[argc++] = strtok(NULL, SEP));

    • 这个循环继续调用 strtok 函数,但是这次传入的第一个参数是 NULL。在 strtok 中,如果第一个参数是 NULL,它会接着处理上次调用时使用的字符串(在这个例子中是 usercommand)。
    • strtok(NULL, SEP):返回下一个标记,直到没有更多的标记可以返回,这时 strtok 会返回 NULL。(所以当 strtok 返回 NULL 的时候就意味着字符串已经被分割完了,并且这个 NULL 也会被赋值给 argv数组(满足了我们的目标:argv 这个数组最后以 NULL 结尾))
    • argv[argc++]:将每个新的标记存储在 argv 数组的下一个位置,并将 argc 加 1。
    • 循环继续直到 strtok 返回 NULL,这意味着所有的标记都已经被提取。

在上面的代码中,while 循环的结束条件是 strtok 函数返回 NULL。这个行为是由 strtok 函数的特性决定的。
strtok 函数在每次调用时会返回下一个标记(token),直到没有更多的标记可以返回。当 strtok 遇到字符串的结尾或者只包含分隔符的字符串时,它会返回 NULL。在 while 循环的条件中,strtok 的返回值被赋值给 argv[argc++],然后条件检查这个赋值的结果是否为 NULL
这里是详细的过程:

  1. 首次调用 strtok(usercommand, SEP) 返回第一个标记,并将这个标记赋值给 argv[argc],然后 argc 自增。

  2. 在随后的 while 循环中,strtok(NULL, SEP) 被调用,它继续处理上次调用 strtok 时留下的字符串。每次调用都会返回下一个标记,直到没有更多的标记。

  3. 每次 strtok 返回一个非 NULL 值时,这个值都会被赋值给 argv[argc],然后 argc 自增,循环继续。

  4. strtok 返回 NULL 时,while 循环的条件不再满足(因为 NULL 等价于逻辑假),循环结束。

while 循环结束的原因是因为 strtok 已经读取了所有的输入字符串,并且没有更多的标记可以返回。这通常意味着已经到达了字符串的末尾,或者字符串中只剩下了分隔符。

我们要用一个变量来存储分割好之后的子串的个数

这个变量就是:argc

int argc = 0;
我们现在的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

int main()
{
    char usercommand[NUM]; // 用来存储用户输入的命令

    char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL
    int argc = 0;              // 用来存储分割好之后的字串的个数

    /********************************************************************************* */
    /******                  1.  打印提示符&&获取用户命令字符串                  ********* */
    /******************************************************************************* */

    getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

    /********************************************************************************* */
    /******                  2.       分割命令                                   ****** */
    /******************************************************************************** */
    argv[argc++] = strtok(usercommand, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (argv[argc++] = strtop(NULL, SEP))
        ;

    // 验证一下我们分割对没有:
    for (int i = 0; argv[i]; i++)
    {
        printf("%d:%s\n", i, argv[i]);
    }

    /******************************************************************************** */
    /******                        3.   执行对应的命令                        ********* */
    /******************************************************************************* */
    printf("%s", usercommand); // 我们用这条语句来显示一下输入的命令到底是怎么样的
    return 0;
}

我们运行一下:
在这里插入图片描述

我们把这个切割字符串的功能封装一下:
// 字符串的切割
void commandSplit(char* in, char* out[])
{
    int argc = 0;              // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;

    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
}

我们在修改一下上面的代码,因为 for 循环只是我们用来验证分割对没有的,我们只有在调试的时候才会用到 for循环里面的代码,所以我们可以加上条件编译

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

#ifdef#endif 是 C 语言预处理器指令,用于条件编译。这些指令允许你根据是否定义了特定的宏来决定是否编译代码的某个部分。

** #ifdef Debug**

#ifdef 是 “if defined” 的缩写。#ifdef Debug 检查是否定义了名为 Debug 的宏。如果 Debug 已经被定义(通常是在编译时通过编译器选项定义的),则编译器会编译 #ifdef#endif 之间的代码。

** #endif**

#endif 表示条件编译的结束。它标志着 #ifdef 或其他条件编译指令(如 #if, #ifndef, #elif)开始的条件编译代码块的结束。

在上面我们的代码中:

#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif

如果 Debug 宏被定义,那么这段代码将被编译和执行,用于打印出分割后的字符串数组 out 中的每个元素。这通常用于调试目的,以确保字符串被正确分割。
如果没有定义 Debug 宏,那么 #ifdef#endif 之间的代码将不会被编译,也不会出现在最终的执行程序中。这样做可以避免在发布版本的程序中包含调试代码,这样可以减少程序的大小和提高性能。

如果我们以后要调试它的话,只需要把Debug 定义出来就行了,像这样:

#define Debug 1
  1. #define:指令告诉预处理器定义一个宏。
  2. Debug:是要定义的宏的名称。
  3. 1:是宏的值。在宏定义中,通常使用 1 或者不赋值(即只有 #define Debug),因为宏定义本身仅仅是一个标识符的替换,而不是一个条件表达式。
    当我们在代码中使用 #ifdef Debug 时,预处理器会检查是否定义了 Debug 宏。由于 Debug 被定义为 1,它被视为真值(在 C 语言中,除了 0 以外的所有值都被视为真值),因此 #ifdef Debug 内的代码块将被包含在编译过程中
如果我们要调试的话,我们需要定义 Debug 我们的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
#define Debug 1
// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

int main()
{
    char usercommand[NUM]; // 用来存储用户输入的命令

    char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

    /********************************************************************************** */
    /******                  1.  打印提示符&&获取用户命令字符串                   ********* */
    /******************************************************************************** */

    getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

    /******************************************************************************** */
    /******                  2.       分割命令                               ********* */
    /******************************************************************************* */

    commandSplit(usercommand, argv);
    /******************************************************************************** */
    /******                        3.   执行对应的命令                        ********* */
    /******************************************************************************** */

    return 0;
}

🏷️ 执行对应的命令

我们创建子进程来执行我们对应的命令,所以我们要用fork() 函数,头文件:unistd.h

// 3.执行对应的命令
    pid_t id = fork();
    if (id < 0)
        return 1;     // 如果创建子进程失败就返回 1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp(),把用户输入的命令传进去。

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }

我们现在的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// #define Debug 1
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

int main()
{
    char usercommand[NUM]; // 用来存储用户输入的命令

    char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

    //  1.打印提示符&&获取用户命令字符串
    getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

    // 2.分割命令
    commandSplit(usercommand, argv);

    // 3.执行对应的命令
    pid_t id = fork();
    if (id < 0)
        return 1;     // 如果创建子进程失败就返回 1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp()

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }
    return 0;
}

我们运行上面的代码:
在这里插入图片描述

哎! 成功了。

但是!我们的 mybash 只能执行一次而系统的bash是执行完这个命令之后还可以执行下一个,所以我们也要优化一下之前的代码,让我们的 mybash 也能周而复始的执行。

这简单呀:外面套一个死循环就行了:

while (1)
{
	// 我们之前的的代码逻辑
}

整体代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// #define Debug 1
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

int main()
{
    while (1)
    {
        char usercommand[NUM]; // 用来存储用户输入的命令

        char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

        //  1.打印提示符&&获取用户命令字符串
        getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

        // 2.分割命令
        commandSplit(usercommand, argv);

        // 3.执行对应的命令
        pid_t id = fork();
        if (id < 0)
            return 1;     // 如果创建子进程失败就返回 1;
        else if (id == 0) // id == 0 ,说明是子进程
        {
            // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
            execvp(argv[0], argv); // 我们使用接口 execvp()

            exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
        }
        else // 这里是父进程
        {
            // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
            pid_t rid = waitpid(id, NULL, 0);
            if (rid > 0) // 说明等待成功了
            {
            }
        }
    }
    return 0;
}

看,我们就能一直输入命令了:
在这里插入图片描述

好了,我们再把执行命令的代码封装一下:

// 执行用户输入的命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0)
        return -1;    // 如果创建子进程失败就返回 -1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp()

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }

    return 0;
}

我们现在的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// #define Debug 1
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

// 执行用户输入的命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0)
        return -1;    // 如果创建子进程失败就返回 -1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp()

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }

    return 0;
}

int main()
{
    while (1)
    {
        char usercommand[NUM]; // 用来存储用户输入的命令

        char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

        //  1.打印提示符&&获取用户命令字符串
        getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

        // 2.分割命令
        commandSplit(usercommand, argv);

        // 3.执行对应的命令
        execute(argv);
    }
    return 0;
}

🏷️ 有一批命令要由父进程来执行----- 内建命令

什么是内建命名:

内建命令就是bash 自己执行的,类似于自己内部的一个函数。

上面的代码我们已经可以实现一个shell 的一些功能了,但是我们会发现这样的一些问题,比如:你运行mybash 之后,可以使用:pwd, ls 之类的命令,但是当我们想做路劲切换的时候缺发现不行,比如:cd ..
因为我们代码中的命令都是 fork()了一个子进程出来执行的。但是如果我们想做路径切换,切换的是当前这个 mybash 的路径,所以要由父进程来执行。

所以我们要区分一下,用户输入的命令是不是内建命令(build-in)
// 3. 检查对应的命令是不是内建命令, 用 1 表示是内建命,用 0 表示不是内建命令
int doBuildin(char *argv[])
{
    if (strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;   // 如果我们使用 cd 命令,那么后面是有路劲的,我们这里用 path来表示
        if (argv[1] == NULL) // 如果用户没有输入路劲,那我们默认是当前路劲:"."
        {
            path = ".";
        }
        else
        {
            path = argv[1]; // else 的话就说明用户输入了路径
        }

        cd(path);
        return 1; // 是内建命令我们 return 1;
    }
    return 0; // 不是内建命令我们就return 0;
}
如果是内建命令cd ,我们如何进行路径切换

这里我们要使用一个新的函数:int chdir(const char *path); 头文件:"stdio.h"

函数原型

int chdir(const char *path);

参数

  • path:指向一个以 null 结尾的字符串,表示要更改到的目标目录的路径。

返回值

  • 如果函数成功执行,返回 0
  • 如果函数执行失败,返回 -1 并设置 errno 以指示错误原因。

行为
chdir 函数将当前工作目录更改为 path 指定的目录。如果 path 是一个绝对路径,它将直接更改到该路径所表示的目录。如果 path 是一个相对路径,它将更改到相对于当前工作目录的路径所表示的目录。

// 路径切换函数
void cd(const char *path)
{
    // 这里我们要使用一个新的函数:int chdir(const char *path); 头文件:"stdio.h"
    // chdir 函数将当前工作目录更改为 path 指定的目录。
    //如果 path 是一个绝对路径,它将直接更改到该路径所表示的目录。
    //如果 path 是一个相对路径,它将更改到相对于当前工作目录的路径所表示的目录
    chdir(path);
}

我们现在的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// #define Debug 1
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 获取当前的用户名
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("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

// 执行用户输入的命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0)
        return -1;    // 如果创建子进程失败就返回 -1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp()

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }

    return 0;
}

// 路径切换函数
void cd(const char *path)
{
    // 这里我们要使用一个新的函数:int chdir(const char *path); 头文件:"stdio.h"
    // chdir 函数将当前工作目录更改为 path 指定的目录。
    //如果 path 是一个绝对路径,它将直接更改到该路径所表示的目录。
    //如果 path 是一个相对路径,它将更改到相对于当前工作目录的路径所表示的目录
    chdir(path);
}

// 3. 检查对应的命令是不是内建命令, 用 1 表示是内建命,用 0 表示不是内建命令
int doBuildin(char *argv[])
{
    if (strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;   // 如果我们使用 cd 命令,那么后面是有路劲的,我们这里用 path来表示
        if (argv[1] == NULL) // 如果用户没有输入路劲,那我们默认是当前路劲:"."
        {
            path = ".";
        }
        else
        {
            path = argv[1]; // else 的话就说明用户输入了路径
        }

        cd(path);
        return 1; // 是内建命令我们 return 1;
    }
    return 0; // 不是内建命令我们就return 0;
}
int main()
{
    while (1)
    {
        char usercommand[NUM]; // 用来存储用户输入的命令

        char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

        //  1.打印提示符&&获取用户命令字符串
        getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

        // 2.分割命令
        commandSplit(usercommand, argv);
        // 3. 检查对应的命令是不是内建命令
        int n = doBuildin(argv);
        if (n) // 如果它是内建命令,我们执行它之后,跳过这个循环剩余的部分,不用再往后走了
            continue;
        // 4.执行对应的命令
        execute(argv);
    }
    return 0;
}

运行上面的代码之后,我们终于可以在自定义的 shell中 进行路径切换了

在这里插入图片描述

所以我们要修改一下我们的代码:

char cwd[1024] 这里面存放的是当前工作目录,是环境变量,环境变量不能是存放在临时的空间中,所以我们把它定义成全局变量

char cwd[1024]; 
// 路径切换函数
void cd(const char *path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp); 
    putenv(cwd);
}

对上面代码的解释:

  1. 定义全局变量 cwd:

    char cwd[1024];
    

    这里定义了一个全局字符数组 cwd,用于存储当前工作目录的路径。由于环境变量需要持久存储,所以 cwd 被定义为全局变量,以便在程序的任何地方都可以访问和修改它。

  2. 定义 cd 函数:

    void cd(const char *path)
    

    这是一个自定义的 cd 函数,接受一个指向字符串的指针 path 作为参数,该参数是要切换到的目标目录路径。

  3. 更改当前工作目录:

    chdir(path);
    

    使用 chdir 函数尝试更改当前工作目录到 path 指定的目录。如果更改失败,chdir 会返回 -1.

  4. 获取当前工作目录:

    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    

    声明一个临时数组 tmp,然后使用 getcwd 函数获取当前工作目录的完整路径,并将其存储在 tmp 中。

  5. 更新全局变量 cwd:

    sprintf(cwd, "PWD=%s", tmp); 
    

    使用 sprintf 函数将 tmp 中的路径格式化为一个字符串,并存储在全局变量 cwd 中。格式化的字符串是 “PWD=路径”,其中 PWD 是一个常用的环境变量,用于存储当前工作目录的路径。

  6. 更新环境变量:

    putenv(cwd);
    

    使用 putenv 函数更新环境变量。putenv 会修改或添加一个环境变量,这里它会更新 PWD 环境变量为 cwd 中存储的路径。这样,任何依赖于 PWD 环境变量的程序都会看到更新后的路径。

这段代码的 cd 函数不仅更改了当前工作目录,还更新了 PWD 环境变量,以便命令行提示符可以显示正确的当前工作目录路径。

我们现在的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 64
#define NUM 1024 // 这里定义一下,我们的命令行最大的输入是 1024 个字节
#define SEP " "
// #define Debug 1
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
char cwd[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 getUserCommand(char *command, int num)
{

    printf("[%s@%s %s]¥", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin);
    if (r == NULL)
        return 1; // 获取失败

    command[strlen(command) - 1] = '\0';
    return 0;
}

// 字符串的切割
void commandSplit(char *in, char *out[])
{
    int argc = 0;                  // 用来存储分割好之后的字串的个数
    out[argc++] = strtok(in, SEP); // 第一次传参:传我们要分割的那个字符串,
    while (out[argc++] = strtok(NULL, SEP))
        ;
#ifdef Debug
    // 验证一下我们分割对没有:
    for (int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

// 执行用户输入的命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0)
        return -1;    // 如果创建子进程失败就返回 -1;
    else if (id == 0) // id == 0 ,说明是子进程
    {
        // exec commmand, 让子进程进行程序替换,目的是执行用户输入的命令
        execvp(argv[0], argv); // 我们使用接口 execvp()

        exit(1); // 子进程执行完成之后,我们不希望它继续往下走,所以我们用 exit 来退出
    }
    else // 这里是父进程
    {
        // 父进程的作用是等待子进程的退出状态,所以我们要使用:waitpid,头文件是:"sys/types.h" 和 “sys/wait.h”
        pid_t rid = waitpid(id, NULL, 0);
        if (rid > 0) // 说明等待成功了
        {
        }
    }

    return 0;
}

// 路径切换函数
void cd(const char *path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp); 
    putenv(cwd);
}

// 3. 检查对应的命令是不是内建命令, 用 1 表示是内建命,用 0 表示不是内建命令
int doBuildin(char *argv[])
{
    if (strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;   // 如果我们使用 cd 命令,那么后面是有路劲的,我们这里用 path来表示
        if (argv[1] == NULL) // 如果用户没有输入路劲,那我们默认是当前路劲:"."
        {
            path = ".";
        }
        else
        {
            path = argv[1]; // else 的话就说明用户输入了路径
        }

        cd(path);
        return 1; // 是内建命令我们 return 1;
    }
    return 0; // 不是内建命令我们就return 0;
}
int main()
{
    while (1)
    {
        char usercommand[NUM]; // 用来存储用户输入的命令

        char *argv[SIZE] = {NULL}; // 分配SIZE个指针,初始值均为NULL

        //  1.打印提示符&&获取用户命令字符串
        getUserCommand(usercommand, sizeof(usercommand)); // 获取用户输入的命令

        // 2.分割命令
        commandSplit(usercommand, argv);
        // 3. 检查对应的命令是不是内建命令
        int n = doBuildin(argv);
        if (n) // 如果它是内建命令,我们执行它之后,跳过这个循环剩余的部分,不用再往后走了
            continue;
        // 4.执行对应的命令
        execute(argv);
    }
    return 0;
}

本章图集:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

仿RabbitMQ实现消息队列服务端(二)

文章目录 ⽹络通信协议设计信道管理模块连接管理模块服务器模块实现 ⽹络通信协议设计 其中⽣产者和消费者都是客⼾端&#xff0c;它们都需要通过⽹络和BrokerServer进⾏通信。具体通信的过程我们使⽤Muduo库来实现&#xff0c;使⽤TCP作为通信的底层协议&#xff0c;同时在这个…

【智能大数据分析 | 实验二】Spark实验:部署Spark集群

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…

如何编写一个优雅的commit message

在Git中&#xff0c;git commit 命令扮演着至关重要的角色。它的主要作用是将暂存区&#xff08;staging area&#xff09;里的改动内容提交到本地仓库&#xff08;repository&#xff09;中&#xff0c;形成一个新的版本或提交&#xff08;commit&#xff09;。这个过程是 Git…

【HarmonyOS】时间处理Dayjs

背景 在项目中经常会使用要时间的格式转换&#xff0c;比如数据库返回一个Date数据&#xff0c;你需要转成2024-10-2的格式&#xff0c;鸿蒙的原生SDK中是没有办法实现的&#xff0c;因此&#xff0c;在这里介绍第三方封装好并且成熟使用的库Dayjs。 安装 切换到Entry文件夹下…

【学习资源】人在环路的机器学习

说明&#xff1a;本文图片和内容来源 Human-in-the-Loop Machine Learning Human-in-the-Loop Machine Learning Active learning and annotation for human-centered AI by Robert (Munro) Monarch, June 2021 介绍Human-in-the-Loop的目标&#xff0c;学习过程&#xff0c…

gdb 调试 linux 应用程序的技巧介绍

使用 gdb 来调试 Linux 应用程序时&#xff0c;可以显著提高开发和调试的效率。gdb&#xff08;GNU 调试器&#xff09;是一款功能强大的调试工具&#xff0c;适用于调试各类 C、C 程序。它允许我们在运行程序时检查其状态&#xff0c;设置断点&#xff0c;跟踪变量值的变化&am…

基于Arduino的宠物食物分配器

创作本文的初衷是本人的一个养宠物的梦想&#xff08;因为家里人对宠物过敏&#xff0c;因此养宠物的action一直没有落实&#xff09;&#xff0c;但是梦想总是要有的哈哈哈哈哈。上周正好是和一个很好的朋友见面&#xff0c;聊到了养宠物的事情&#xff0c;她大概是讲到了喂宠…

震撼!工业史上第一家万级别规模的工业数字化设备效果图平台

耗时八年打造&#xff0c;国内第一家万级别规模的工业数字化设备效果图平台 平台&#xff1a;www.kingview3d.cn 创作者&#xff1a;kingview3d郭工 行业&#xff1a;煤矿综合自动化、污水处理、净水处理、楼宇暖通、环保工程、医药废水处理、二供、无负压加压站、提升泵站、一…

《NoSQL》非关系型数据库MongoDB 学习笔记!

Mongo基础&#xff1a; 使用数据库&#xff1a; 使用use 命令 后面跟着要使用的数据库名字即可&#xff0c; 例如&#xff1a;use cities, 值得注意的是&#xff0c; mongo中不像mysql&#xff0c; 还需要先创建数据库&#xff0c;后访问&#xff0c; mongo中&#xff0c;你无…

【WebGis开发 - Cesium】如何确保Cesium场景加载完毕

目录 引言一、监听场景加载进度1. 基础代码2. 加工代码 二、进一步封装代码1. 已知存在的弊端2. 封装hooks函数 三、使用hooks方法1. 先看下效果2. 如何使用该hooks方法 三、总结 引言 本篇为Cesium开发的一些小技巧。 判断Cesium场景是否加载完毕这件事是非常有意义的。 加载…

在 Elasticsearch Serverless 上使用 Eland

作者&#xff1a;来自 Elastic Quentin Pradet 本博客将向你展示如何使用 Eland 将机器学习模型导入 Elasticsearch Serverless&#xff0c;然后如何使用类似 Pandas 的 API 探索 Elasticsearch。 Elasticsearch Serverless 中的 NLP 自 Elasticsearch 8.0 起&#xff0c;可以…

SQL专项练习第二天

在数据处理和分析中&#xff0c;Hive 是一个强大的工具。本文将通过五个 Hive 相关的问题展示其在不同场景下的应用技巧。 先在home文件夹下建一个hivedata文件夹&#xff0c;把我们所需的数据写成txt文件导入到/home/hivedata/文件夹下面。 一、找出连续活跃 3 天及以上的用户…

【AI论文精读1】针对知识密集型NLP任务的检索增强生成(RAG原始论文)

目录 一、简介一句话简介作者、引用数、时间论文地址开源代码地址 二、摘要三、引言四、整体架构&#xff08;用一个例子来阐明&#xff09;场景例子&#xff1a;核心点&#xff1a; 五、方法 &#xff08;架构各部分详解&#xff09;5.1 模型1. RAG-Sequence Model2. RAG-Toke…

Python+Matplotlib创建y=sinx、y=cosx、y=sinx+cosx可视化

y sin x (奇函数)&#xff1a; 图像关于原点对称。 对于任何 x&#xff0c;sin(-x) -sin(x)&#xff0c;符合奇函数定义。 y cos x (偶函数)&#xff1a; 图像关于 y 轴对称。 对于任何 x&#xff0c;cos(-x) cos(x)&#xff0c;符合偶函数定义。 y sin x cos x (既…

安全帽头盔检测数据集 3类 12000张 安全帽数据集 voc yolo

安全帽头盔检测数据集 3类 12000张 安全帽数据集 voc yolo 安全帽头盔检测数据集介绍 数据集名称 安全帽头盔检测数据集 (Safety Helmet and Person Detection Dataset) 数据集概述 该数据集专为训练和评估基于YOLO系列目标检测模型&#xff08;包括YOLOv5、YOLOv6、YOLOv7…

LabVIEW机床加工监控系统

随着制造业的快速发展&#xff0c;机床加工的效率与稳定性成为企业核心竞争力的关键。传统的机床监控方式存在效率低、无法远程监控的问题。为了解决这些问题&#xff0c;开发了一种基于LabVIEW的机床加工监控系统&#xff0c;通过实时监控机床状态&#xff0c;改进生产流程&am…

Spring MVC__入门

目录 一、SpringMVC简介1、什么是MVC2、什么是SpringMVC 二、Spring MVC实现原理2.1核心组件2.2工作流程 三、helloworld1、开发环境2、创建maven工程3、配置web.xml4、创建请求控制器5、创建springMVC的配置文件6、测试HelloWorld7、总结 一、SpringMVC简介 1、什么是MVC MV…

html5 + css3(上)

目录 HTML初识基础认知web标准vscode的简介和使用注释 HTML标签学习排版标签标题和段落换行和水平线标签 文本格式化标签媒体标签图片标签图片-基本使用图片-属性 路径绝对路径相对路径 音频标签视频标签链接标签 HTML基础列表标签列表-无序和有序列表-自定义 表格标签表格-使用…

【JAVA开源】基于Vue和SpringBoot的周边产品销售网站

本文项目编号 T 061 &#xff0c;文末自助获取源码 \color{red}{T061&#xff0c;文末自助获取源码} T061&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

Java网络通信—UDP

0.小记 1.udp通信不需要建立socket管道&#xff0c;一边只管发&#xff0c;一边只管收 2.客户端&#xff1a;将数据&#xff08;byte&#xff09;打包成包裹&#xff08;DatagramPacket&#xff09;&#xff0c;写上地址&#xff08;IP端口&#xff09;&#xff0c;通过快递站&…