Linux(10)——第二个小程序(自制shell)

news2025/6/5 21:35:42

目录

​编辑

一、引言与动机

📝背景

📝主要内容概括

二、全局数据

三、环境变量的初始化

✅ 代码实现

 四、构造动态提示符

✅ 打印提示符函数

✅ 提示符生成函数 

✅获取用户名函数

✅获取主机名函数

✅获取当前目录名函数

五、命令的读取与解析

✅读取用户输入函数

✅命令解析函数

六、内建命令的检测与执行

💡先来回答两个疑问:

✅检测函数

✅cd的实现

 ✅echo的实现

七、重定向的处理

✅读取函数

✅去空格函数:

✅执行函数

八、执行流程 

九、源码


一、引言与动机

📝背景

我们从之前的文章中学习了linux的相关知识,包括但不限于进程管理、文件的重定向以及环境变量,基于此我们来自制一个简化版的shell,也是对前期学习的内容的一个运用。

📝主要内容概括

我们将实现以下功能:

  • 全局数据和环境变量初始化

  • 命令提示符的构建

  • 命令行输入与解析机制

  • 内建命令(cdecho)的实现

  • 输入/输出重定向的处理

  • 外部程序的执行流程(fork + execvp + waitpid

二、全局数据

  • #define COMMAND_SIZE 1024

  • #define FORMAT "[%s@%s %s]# ",格式化字符串

  • char *g_argv[MAXARGC];int g_argc:保存切分后的命令及参数

  • char *g_env[MAX_ENVS]int g_envs:复制并维护环境变量列表

  • std::unordered_map<std::string,std::string> alias_list:(预留的)别名映射

  • 重定向相关:int redir; std::string filename;

  • 记录当前目录:char cwd[1024]; char cwdenv[1024];

  • 记录上次命令退出码:int lastcode;

 

三、环境变量的初始化

✅ 代码实现

这里主要是为了后续实现的功能提供自己环境变量,这样也可使得修改更加方便。

void InitEnv()    
{    
    extern char **environ;//从#include <cstdlib>中获取环境变量表    
    memset(g_env, 0, sizeof(g_env));//清空自建的环境变量表    
    g_envs = 0;    
   
    //1. 获取环境变量    
    for(int i = 0; environ[i]; i++)    
    {        
        g_env[i] = (char*)malloc(strlen(environ[i])+1);    
        strcpy(g_env[i], environ[i]);    
        g_envs++;    
    }    
    g_env[g_envs++] = (char*)"HAHA=for_test"; //测试导入环境变量   
    g_env[g_envs] = NULL;//注意末尾置空
    
    //2. 导成环境变量    
    for(int i = 0; g_env[i]; i++)    
    {    
        putenv(g_env[i]);    
    }    
    environ = g_env; //3.配置为全局的变量 
}

 四、构造动态提示符

我们在使用linux时常要变换所在路径,所以我们这里也实现一个动态变换的提示符:

示例效果:

[alice@myhost project]#  //为了区分这里用#

✅ 打印提示符函数

刷新缓冲区来打印。

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

✅ 提示符生成函数 

void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}

这里提一下snprintf函数:

在 C/C++ 中,snprintf 是一个用于格式化字符串的函数:

int snprintf(char *str, size_t size, const char *format, ...);

参数:

str:目标字符数组,格式化的字符串将写入此处。

size:目标字符数组的最大长度(包括结尾的空字符 \0),用于限制写入字符数以防止缓冲区溢出。

format:格式化字符串,类似于 printf 的格式说明符(如 %d, %s, %f 等)。

...:可变参数列表,对应格式化字符串中的占位符。

返回值:

成功时:返回格式化后字符串的长度(不包括结尾的 \0),即使部分字符因 size 限制未写入。

失败时:返回负值(某些实现中可能不同,需检查文档)。

敲黑板:

返回的长度是完整格式化字符串的长度,即使因 size 限制只写入部分字符。

✅获取用户名函数

const char* GetUserName()
{
    const char* name = getenv("USER");
    return name == NULL ? "None" : name;
}

✅获取主机名函数

const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

✅获取当前目录名函数

这里我们要做一下根目录的判断,如果是根目录就直接返回就行,如果不是根目录就找出/后面的字符串,没找到就报错。

std::string DirName(const char* pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if (dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if (pos == std::string::npos) return "BUG?";
    return dir.substr(pos + 1);
}

实现展示:

五、命令的读取与解析

✅读取用户输入函数

这里需要注意将\n删除。

bool GetCommandLine(char* out, int size)
{
    char* c = fgets(out, size, stdin);
    if (c == NULL) return false;
    out[strlen(out) - 1] = 0; // 清理\n
    if (strlen(out) == 0) return false;
    return true;
}

简单提一下fgets函数:

在 C/C++ 中,fgets 是一个用于从文件中读取字符串的函数。

char *fgets(char *str, int size, FILE *stream);

参数:

str:目标字符数组,用于存储读取的字符串(包括换行符 \n 和结尾的空字符 \0)。

size:最多读取的字符数(包括 \0),防止缓冲区溢出。

stream:文件流指针(如 stdin、文件句柄等)。

返回值:

成功:返回 str(指向读取的字符串)。

失败或文件末尾(EOF):返回 nullptr。

敲黑板:

读取到换行符 \n 或文件末尾会停止,换行符(如果存在)会包含在 str 中。 

✅命令解析函数

bool CommandParse(char* commandline)
{
#define SEP " "
    g_argc = 0;
    // 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline, SEP);
    while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

 这里简单提一下strtok函数:

在 C/C++ 中,strtok 是一个用于字符串分割的函数。

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

参数:

str:要分割的字符串(第一次调用时传入,之后传入 nullptr 以继续处理同一字符串)。

delim:包含分隔符的字符串,每个字符都被视为一个分隔符。

返回值:

成功:返回指向下一个 token 的指针。

失败或无更多 token:返回 nullptr。

敲黑板:

strtok 会修改原字符串(在分隔符处插入 \0),因此输入字符串必须是可修改的(非 const 或字符串字面量)。 

实现展示:

六、内建命令的检测与执行

💡先来回答两个疑问:

第一个疑问:什么是内建命令

内建命令是在shell自身实现的命令,不依赖系统外部的可执行文件。例如:

cd:切换当前目录

alias:设置别名

export:设置环境变量

echo:打印信息

exit:退出 shell

我们这里会实现前三个。

第二个疑问:为什么内建命令单独执行

主要原因是他们只有在当前shell进程中执行才可以真正的影响到shell的状态。

比如:cd 改变当前目录(影响 shell),export 改变环境变量(供子进程使用)以及exit 终止当前shell,这类命令交给子进程执行就完全失去作用了。

✅检测函数

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if (cmd == "cd")
    {
        Cd();
        return true;
    }
    else if (cmd == "echo")
    {
        Echo();
        return true;
    }
    else if (cmd == "alias")
    {
        std::string nickname = g_argv[1];
        alias_list.insert(k, v);
    }else if (cmd == "export")
    {
        // todo
    }

    return false;
}

✅cd的实现

bool Cd()
{
    if (g_argc == 1)
    {
        std::string home = GetHome();
        if (home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        if (where == "-")
        {
            // Todo
        }
        else if (where == "~")
        {
            // Todo
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

实现展示:

 ✅echo的实现

void Echo()    
{    
    if (g_argc >= 2)    
    {    
        for (int i = 1; i < g_argc; ++i)    
        {    
            std::string opt = g_argv[i];    
      
            if (opt == "$?")    
            {    
                std::cout << lastcode;    
            }    
            else if (opt[0] == '$')    
            {    
                std::string env_name = opt.substr(1);    
                const char *env_value = getenv(env_name.c_str());    
                if (env_value)    
                    std::cout << env_value;    
            }    
            else    
            {    
                std::cout << opt;    
            }    
    
            if (i < g_argc - 1)    
                std::cout << " ";    
        }    
        std::cout << std::endl;    
    }    
} 

七、重定向的处理

✅读取函数

void RedirCheck(char cmd[])
{
    redir = NONE_REDIR; // 默认初始化为只读
    filename.clear(); // 将之前的文件名清空
    int start = 0;
    int end = strlen(cmd) - 1;

    //"ls -a -l >> file.txt" > >> <
    while (end > start)
    {
        if (cmd[end] == '<')
        {
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            redir = INPUT_REDIR;
            filename = cmd + end;
            break;
        }
        else if (cmd[end] == '>')
        {
            if (cmd[end - 1] == '>')
            {
                //>>
                cmd[end - 1] = 0;
                redir = APPEND_REDIR;
            }
            else
            {
                //>
                redir = OUTPUT_REDIR;
            }
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            filename = cmd + end;
            break;
        }
        else
        {
            end--;
        }
    }
}

✅去空格函数:

void TrimSpace(char cmd[], int& end)
{
    while (isspace(cmd[end]))
    {
        end++;
    }
}

✅执行函数

int Execute()
{
    pid_t id = fork();
    if (id == 0)
    {
        int fd = -1;
        // 子进程检测重定向情况
        if (redir == INPUT_REDIR)
        {
            fd = open(filename.c_str(), O_RDONLY);
            if (fd < 0) exit(1);
            dup2(fd, 0);
            close(fd);
        }
        else if (redir == OUTPUT_REDIR)
        {
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if (fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else if (redir == APPEND_REDIR)
        {
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
            if (fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else
        {
            // todo
        }
        // 进程替换,会影响重定向的结果吗?不影响
        //child
        execvp(g_argv[0], g_argv); // 进程替换函数,执行成功后续代码不执行,失败就调用exit(1)
        exit(1);
    }
    int status = 0;
    // father
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待子进程退出
    if (rid > 0)
    {
        lastcode = WEXITSTATUS(status); // 更新进程退出码
    }
    return 0;
}

这里说明一下这几个函数:

第一个函数:int dup2(int oldfd, int newfd);

作用:在 Linux 环境下,dup2 是一个 POSIX 系统调用,用于复制文件描述符,常用于重定向文件描述符(如标准输入、输出或错误输出)。

参数:

oldfd:要复制的现有文件描述符(如打开的文件、管道、标准输入/输出等)。

newfd:目标文件描述符编号,oldfd 将被复制到此编号。

返回值:

成功:返回 newfd(目标文件描述符)。

失败:返回 -1,并设置 errno 表示错误原因(如 EBADF 表示无效文件描述符)。 

敲黑板:

这里可能你会有一个疑问,那就是为什么是oldfd复制到newfd而不是newfd复制到oldfd,这里我们一定要清楚这里的新旧指的是这个文件是否被使用或是否被打开,那么就是被使用的(oldfd)复制到未被使用的(newfd)。

第二个函数:int open(const char *pathname, int flags, mode_t mode);

作用:在 Linux 环境下,open 是一个 POSIX 系统调用,用于打开文件或创建文件,获取文件描述符以进行读写操作。

参数:

pathname:要打开或创建的文件路径(绝对或相对路径)。

flags:控制文件打开方式的标志(如只读、只写、读写等)。

常用标志:

O_RDONLY:只读。

O_WRONLY:只写。

O_RDWR:读写。

O_CREAT:如果文件不存在则创建。

O_TRUNC:如果文件存在且为写模式,清空文件内容。

O_APPEND:写入时追加到文件末尾。

多个标志可通过位或(|)组合使用。

mode:指定新创建文件的权限(如 0644),仅在 flags 包含 O_CREAT 时有效。

返回值:

成功:返回文件描述符(非负整数)。

失败:返回 -1,并设置 errno 表示错误(如 ENOENT 表示文件不存在)。 

第三个函数:int execvp(const char *file, char *const argv[]); 

作用:在 Linux 环境下,execvp 是一个 POSIX 系统调用,用于执行新程序,替换当前进程的镜像。

参数:

file:要执行的程序名(可以是命令名如 "ls",无需完整路径,execvp 会搜索 PATH 环境变量)。argv:指向参数数组的指针,包含程序名和传递给程序的参数,以 nullptr 结尾。

返回值:

成功:不返回(当前进程镜像被替换)。

失败:返回 -1,并设置 errno 表示错误(如 ENOENT 表示程序不存在)。

实现展示:

sort < unsorted.txt

ls -a -l > file.txt

ls -a -l >> file.txt

八、执行流程 

int main()
{
    InitEnv();

    while (true)
    {
        PrintCommandPrompt();

        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        RedirCheck(commandline);

        if (!CommandParse(commandline))
            continue;

        if (CheckAndExecBuiltin())
            continue;

        Execute();
    }

    return 0;
}

 主循环:

  1. 打印提示符

  2. 读取一行用户输入(回车前)

  3. 分析是否有重定向,截断原命令并提取文件名

  4. 将命令行拆分为 g_argc/g_argv[]

  5. 检测并执行内建命令(若是则跳过后续步骤)

  6. 启动子进程执行外部命令

九、源码

#include <iostream>
#include <ctype.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;

#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;

std::unordered_map<std::string, std::string> alias_list;

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

int redir = NONE_REDIR;
std::string filename;

char cwd[1024];
char cwdenv[1024];

int lastcode = 0;

const char* GetUserName()
{
    const char* name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char* GetPwd()
{
    const char* pwd = getcwd(cwd, sizeof(cwd));
    if (pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char* GetHome()
{
    const char* home = getenv("HOME");
    return home == NULL ? "" : home;
}

void InitEnv()
{
    extern char** environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;

    for (int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = (char*)"HAHA=for_test"; 
    g_env[g_envs] = NULL;

    for (int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

bool Cd()
{
    if (g_argc == 1)
    {
        std::string home = GetHome();
        if (home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];
        if (where == "-")
        {
            // Todu
        }
        else if (where == "~")
        {
            // Todu
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if (g_argc >= 2)
    {
        for (int i = 1; i < g_argc; ++i)
        {
            if (std::string(g_argv[i]) == "$?")
            {
                std::cout << lastcode;
            }
            else if (g_argv[i][0] == '$')
            {
                const char* env_value = getenv(g_argv[i] + 1);
                if (env_value)
                    std::cout << env_value;
            }
            else
            {
                std::cout << g_argv[i];
            }

            if (i < g_argc - 1)
                std::cout << " ";
        }
        std::cout << std::endl;
    }
    else
    {
        std::cout << std::endl;
    }
}


std::string DirName(const char* pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if (dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if (pos == std::string::npos) return "BUG?";
    return dir.substr(pos + 1);
}

void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

bool GetCommandLine(char* out, int size)
{
    char* c = fgets(out, size, stdin);
    if (c == NULL) return false;
    out[strlen(out) - 1] = 0; 
    if (strlen(out) == 0) return false;
    return true;
}

bool CommandParse(char* commandline)
{
#define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, SEP);
    while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for (int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc: %d\n", g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if (cmd == "cd")
    {
        Cd();
        return true;
    }
    else if (cmd == "echo")
    {
        Echo();
        return true;
    }
    else if (cmd == "export")
    {
    }
    else if (cmd == "alias")
    {
    }

    return false;
}

int Execute()
{
    pid_t id = fork();
    if (id == 0)
    {
        int fd = -1;
        if (redir == INPUT_REDIR)
        {
            fd = open(filename.c_str(), O_RDONLY);
            if (fd < 0) exit(1);
            dup2(fd, 0);
            close(fd);
        }
        else if (redir == OUTPUT_REDIR)
        {
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if (fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else if (redir == APPEND_REDIR)
        {
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
            if (fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else
        {
        }
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

void TrimSpace(char cmd[], int& end)
{
    while (isspace(cmd[end]))
    {
        end++;
    }
}

void RedirCheck(char cmd[])
{
    redir = NONE_REDIR;
    filename.clear();
    int start = 0;
    int end = strlen(cmd) - 1;

    while (end > start)
    {
        if (cmd[end] == '<')
        {
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            redir = INPUT_REDIR;
            filename = cmd + end;
            break;
        }
        else if (cmd[end] == '>')
        {
            if (cmd[end - 1] == '>')
            {
                //>>
                cmd[end - 1] = 0;
                redir = APPEND_REDIR;
            }
            else
            {
                //>
                redir = OUTPUT_REDIR;
            }
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            filename = cmd + end;
            break;
        }
        else
        {
            end--;
        }
    }
}

int main()
{
    InitEnv();

    while (true)
    {
        PrintCommandPrompt();

        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        RedirCheck(commandline);

        if (!CommandParse(commandline))
            continue;

        if (CheckAndExecBuiltin())
            continue;

        Execute();
    }

    return 0;
}

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

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

相关文章

代码随想录算法训练营 Day59 图论Ⅸ dijkstra优化版 bellman_ford

图论 题目 47. 参加科学大会&#xff08;第六期模拟笔试&#xff09; 改进版本的 dijkstra 算法&#xff08;堆优化版本&#xff09; 朴素版本的 dijkstra 算法解法的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 时间复杂度与 n 有关系&#xff0c;与边无关系 类似于 prim 对应点多…

【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)

文章目录 蜜罐1. 什么是蜜罐&#xff1f;2. 开源蜜罐搭建与使用3. HFish 开源蜜罐详解安装步骤使用指南关闭方法 总结 蜜罐 1. 什么是蜜罐&#xff1f; 蜜罐&#xff08;Honeypot&#xff09;是一种主动防御技术&#xff0c;通过模拟存在漏洞的系统或服务&#xff08;如数据库…

汽车总线分析总结(CAN、LIN、FlexRay、MOST、车载以太网)

目录 一、汽车总线技术概述 二、主流汽车总线技术对比分析 1. CAN总线&#xff08;Controller Area Network&#xff09; 2. LIN总线&#xff08;Local Interconnect Network&#xff09; 3. FlexRay总线 4. MOST总线&#xff08;Media Oriented Systems Transport&#x…

MyBatisPlus--条件构造器及自定义SQL详解

条件构造器 在前面学习快速入门的时候&#xff0c;练习的增删改查都是基于id去执行的&#xff0c;但是在实际开发业务中&#xff0c;增删改查的条件往往是比较复杂的&#xff0c;因此MyBatisPlus就提供了一个条件构造器来帮助构造复杂的条件。 MyBatisPlus支持各种复杂的wher…

OVD开放词汇检测 Detic 训练COCO数据集实践

0、引言 纯视觉检测当前研究基本比较饱和&#xff0c;继续创新提升空间很小&#xff0c;除非在CNN和transformer上提出更强基础建模方式。和文本结合是当前的一大趋势&#xff0c;也是计算机视觉和自然语言处理结合的未来趋势&#xff0c;目前和文本结合的目标检测工作还是有很…

docker、ctr、crictl命令简介与使用

概述 在使用k3s过程中&#xff0c;经常需要使用ctr和crictl两个命令&#xff0c;本文记录一下。 ctr 类似docker命令是docker-shim容器运行时的客户端工具&#xff0c;ctr是Containerd的客户端工具。一个简单的CLI接口&#xff0c;用作Containerd本身的一些调试用途&#xf…

WEB3——什么是ABI

怎么获得ABI&#xff1f; 在编译完合约后&#xff0c;可以在左边下面点击复制ABI ABI&#xff08;Application Binary Interface&#xff0c;应用二进制接口&#xff09;是用来让前端或服务端 JavaScript 代码与智能合约进行交互的桥梁&#xff0c;它描述了合约的函数、事件和…

嵌入式软件--stm32 DAY 8.5 基础复习总结

1.时钟树 在数据手册里面&#xff0c;有一张密密麻麻的图&#xff0c;正是时钟系统里的时钟树。 对于时钟&#xff0c;我们注意有两点。一个是系统时钟SYSCLK,一个是依赖外部晶振生成的RTC. RTC以外部低速晶振作为时钟源或者外部高速晶振128分频后作为时钟源&#xff0c;又或者…

MMRL: Multi-Modal Representation Learning for Vision-Language Models(多模态表示学习)

摘要 预训练的VLMs,对于跨任务的迁移学习至关重要&#xff0c;然而&#xff0c;在few-shot数据集上微调会导致过拟合&#xff0c;降低在新任务上的性能。为解决这个问题&#xff0c;提出一种新的多模态表征学习框架&#xff08;MMRL&#xff09;,该框架引入了一个共享、可学习…

rsync服务的搭建

目录 一、rsync介绍 rsync的安装 二、rsync的语法 三、rsync命令使用 1. 本机同步 2. 远程同步 四、rsync作为服务使用 1、尝试启动rsync程序 2、rsync的配置文件介绍 注意事项&#xff1a; 3. rsyncinotify实时同步 3.依赖服务托管xinetd&#xff08;CentOS 6中rs…

vscode 配置 QtCreat Cmake项目

1.vscode安装CmakeTool插件并配置QT中cmake的路径&#xff0c;不止这一处 2.cmake生成器使用Ninja&#xff08;Ninja在安装QT时需要勾选&#xff09;&#xff0c;可以解决[build] cc1plus.exe: error: too many filenames given; type ‘cc1plus.exe --help’ for usage 编译时…

HTML实现端午节主题网站:龙舟争渡,凭吊祭江诵君赋。

名人说&#xff1a;龙舟争渡&#xff0c;助威呐喊&#xff0c;凭吊祭江诵君赋。——苏轼《六幺令天中节》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、项目概览&#xff1a;传统与现代的技术碰撞1. 核心特…

uniapp uni-id 如果是正式项目,需自行实现发送邮件的相关功能

(3) 使用云对象sendEmailCode 发送邮箱验证码&#xff0c;报错送邮箱验证码失败 Error: 已启动测试模式&#xff0c;直接使用&#xff1a;123456作为邮箱验证码即可。 如果是正式项目&#xff0c;需自行实现发送邮件的相关功能 - DCloud问答 uni-id 没有实现邮箱验证码逻辑&am…

C++学习-入门到精通【12】文件处理

C学习-入门到精通【12】文件处理 目录 C学习-入门到精通【12】文件处理一、文件和流二、创建顺序文件三、从顺序文件读取数据文件定位指针对之前的程序进行修改&#xff1a;贷款查询程序 四、更新顺序文件五、随机存取文件1.创建随机存取文件2.修改程序&#xff1a;贷款处理程序…

记一次 Starrocks be 内存异常宕机

突发性 be 内存飙高&#xff0c;直至被系统 kill 掉&#xff0c;be 内存如下&#xff1a;其中 starrocks_be_update_mem_bytes 指标打满&#xff0c;重启也是如此 [rootlocalhost bin]# curl -XGET -s http://192.168.1.49:8040/metrics | grep "^starrocks_be_.*_mem_b…

LangChain-结合GLM+SQL+函数调用实现数据库查询(一)

业务流程 实现步骤 1. 加载数据库配置 在项目的根目录下创建.env 文件&#xff0c;设置文件内容&#xff1a; DB_HOSTxxx DB_PORT3306 DB_USERxxx DB_PASSWORDxxx DB_NAMExxx DB_CHARSETutf8mb4 加载环境变量&#xff0c;从 .env 文件中读取数据库配置信息 使用 os.getenv…

2025年渗透测试面试题总结-匿名[校招]安全工程师(甲方)(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 匿名[校招]安全工程师(甲方) 1. 介绍自己熟悉的渗透领域 2. 编程语言与开发能力 3. 实习工作内容与流程 …

PySide6 GUI 学习笔记——常用类及控件使用方法(地址类QUrl)

文章目录 地址类QUrl主要功能URL 格式介绍常见 scheme&#xff08;协议&#xff09;类型QUrl 类常用方法常用方法示例典型应用场景 地址类QUrl QUrl 是 PySide6.QtCore 模块中的一个类&#xff0c;用于处理和操作 URL&#xff08;统一资源定位符&#xff09;。它可以解析、构建…

任务23:创建天气信息大屏Django项目

任务描述 知识点&#xff1a; Django 重 点&#xff1a; Django创建项目Django视图函数Django路由Django静态文件Django渲染模板 内 容&#xff1a; 使用PyCharm创建大屏项目渲染大屏主页 任务指导 1. 使用PyCharm创建大屏项目。 创建weather项目配置虚拟环境创建ch…

数学分析——一致性(均匀性)和收敛

目录 1. 连续函数 1.1 连续函数的定义 1.2 连续函数的性质 1.2.1 性质一 1.2.2 性质二 1.2.3 性质三 1.2.4 性质四 2. 一致连续函数 2.1 一致连续函数的定义 2.2 一致连续性定理(小间距定理)(一致连续函数的另一种定义) 2.3 一致连续性判定法 2.4 连…