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

目錄

?編輯

一、引言與動機

📝背景

📝主要內容概括

二、全局數據

三、環境變量的初始化

? 代碼實現

?四、構造動態提示符

? 打印提示符函數

? 提示符生成函數?

?獲取用戶名函數

?獲取主機名函數

?獲取當前目錄名函數

五、命令的讀取與解析

?讀取用戶輸入函數

?命令解析函數

六、內建命令的檢測與執行

💡先來回答兩個疑問:

?檢測函數

?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; // 清理\nif (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}// 進程替換,會影響重定向的結果嗎?不影響//childexecvp(g_argv[0], g_argv); // 進程替換函數,執行成功后續代碼不執行,失敗就調用exit(1)exit(1);}int status = 0;// fatherpid_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 3int 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.pswp.cn/pingmian/83690.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83690.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/83690.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

環境變量深度解析:從配置到內核的全鏈路指南

文章目錄 一、基礎概念與核心作用二、常見環境變量三、操作指南&#xff1a;從查看、修改到調試3.1 快速查詢3.2 PATH 原理與配置實踐3.2.1 命令執行機制3.2.2 路徑管理策略 四、編程接口與內存模型4.1 環境變量的內存結構4.2 C 語言訪問方式4.2.1 直接訪問&#xff08;main 參…

結合Jenkins、Docker和Kubernetes等主流工具,部署Spring Boot自動化實戰指南

基于最佳實踐的Spring Boot自動化部署實戰指南,結合Jenkins、Docker和Kubernetes等主流工具,提供從環境搭建到生產部署的完整流程: 一、環境準備與工具選型?? ??1.基礎設施?? ??Jenkins服務器??:安裝Jenkins LTS版本,配置JDK(推薦JDK 11+)及Maven/Gradle插…

動態規劃---股票問題

1.在推狀態轉移方程的途中&#xff0c;箭頭的起始點表示前一天的狀態&#xff0c;箭頭的終點是當天的狀態 2.當動態規劃中涉及到多狀態&#xff0c;且狀態之間可以相互轉換&#xff0c;要畫圖去分析 1.買賣股票的最佳時機含冷凍期 題目鏈接&#xff1a;309. 買賣股票的最佳時機…

ObjectMapper 在 Spring 統一響應處理中的作用詳解

ObjectMapper 是 Jackson 庫的核心類&#xff0c;專門用于處理 JSON 數據的序列化&#xff08;Java 對象 → JSON&#xff09;和反序列化&#xff08;JSON → Java 對象&#xff09;。在你提供的代碼中&#xff0c;它解決了字符串響應特殊處理的關鍵問題。 一、為什么需要 Obj…

總結這幾個月來我和AI一起開發并上線第一個應用的使用經驗

副標題&#xff1a; 當“手殘”前端遇到AI隊友&#xff0c;我的音樂小站譜貝誕生記 大家好&#xff0c;我最近干了件“不務正業”的事——**獨立開發并上線了一個完整的網站 作為一個前端“手殘黨”&#xff08;還在努力學習中&#x1f605;&#xff09;&#xff0c;這次能成功…

【大模型:知識圖譜】--5.neo4j數據庫管理(cypher語法2)

目錄 1.節點語法 1.1.CREATE--創建節點 1.2.MATCH--查詢節點 1.3.RETURN--返回節點 1.4.WHERE--過濾節點 2.關系語法 2.1.創建關系 2.2.查詢關系 3.刪除語法 3.1.DELETE 刪除 3.2.REMOVE 刪除 4.功能補充 4.1.SET &#xff08;添加屬性&#xff09; 4.2.NULL 值 …

結構體指針與非指針 問題及解決

問題描述 第一段位于LCD.h和LCD.c中&#xff0c; 定義個一個結構體lcd_params&#xff0c;并直接給與指針名*p_lcd_params; 我發現我在調用這個結構體時&#xff0c;即在LCD.c中&#xff0c;使用指針類型定義的 static p_lcd_params p_array_lcd[LCD_NUM]; static p_lcd_par…

【設計模式-3.7】結構型——組合模式

說明&#xff1a;本文介紹結構型設計模式之一的組合模式 定義 組合模式&#xff08;Composite Pattern&#xff09;又叫作整體-部分&#xff08;Part-Whole&#xff09;模式&#xff0c;它的宗旨是通過將單個對象&#xff08;葉子節點&#xff09;和組合對象&#xff08;樹枝…

【TMS570LC4357】之相關驅動開發學習記錄2

系列文章目錄 【TMS570LC4357】之工程創建 【TMS570LC4357】之工程配置修改 【TMS570LC4357】之HALCOGEN使用 【TMS570LC4357】之相關問題及解決 【TMS570LC4357】之相關驅動開發學習記錄1 ——————————————————— 前言 記錄筆者在第一次使用TMS570過程中對…

3D Gaussian splatting 05: 代碼閱讀-訓練整體流程

目錄 3D Gaussian splatting 01: 環境搭建3D Gaussian splatting 02: 快速評估3D Gaussian splatting 03: 用戶數據訓練和結果查看3D Gaussian splatting 04: 代碼閱讀-提取相機位姿和稀疏點云3D Gaussian splatting 05: 代碼閱讀-訓練整體流程3D Gaussian splatting 06: 代碼…

【黑馬程序員uniapp】項目配置、請求函數封裝

黑馬程序員前端項目uniapp小兔鮮兒微信小程序項目視頻教程&#xff0c;基于Vue3TsPiniauni-app的最新組合技術棧開發的電商業務全流程_嗶哩嗶哩_bilibili 參考 有代碼&#xff0c;還有app、h5頁面、小程序的演示 小兔鮮兒-vue3ts-uniapp-一套代碼多端部署: 小兔鮮兒-vue3ts-un…

前端使用 preview 插件預覽docx文件

目錄 前言一 引入插件二 JS 處理 前言 前端使用 preview 插件預覽docx文件 一 引入插件 建議下載至本地&#xff0c;靜態引入&#xff0c;核心的文件已打包&#xff08;前端使用 preview 插件預覽docx文件&#xff09;&#xff0c;在文章目錄處下載至本地&#xff0c;復制在項…

如何在運動中保護好半月板?

文章目錄 引言I 半月板的作用穩定作用緩沖作用潤滑作用II 在跳繩運動中保護好半月板III 半月板損傷自測IV 半月板“殺手”半月板損傷必須滿足四個因素:消耗品引言 膝蓋是連接大腿骨和小腿骨的地方,在兩部分骨頭的連接處,墊著兩片半月形的纖維軟骨板,這就是半月板。半月板分…

安科瑞防逆流方案落地內蒙古中高綠能光伏項目,筑牢北疆綠電安全防線

一、項目概況 內蒙古阿拉善中高綠能能源分布式光伏項目&#xff0c;位于內蒙古烏斯太鎮&#xff0c;裝機容量為7MW&#xff0c;采用自發自用、余電不上網模式。 用戶配電站為35kV用戶站&#xff0c;采用兩路電源單母線分段系統。本項目共設置12臺35/0.4kV變壓器&#xff0c;在…

1.3 fs模塊詳解

fs 模塊詳解 Node.js 的 fs 模塊提供了與文件系統交互的能力&#xff0c;是服務器端編程的核心模塊之一。它支持同步、異步&#xff08;回調式&#xff09;和 Promise 三種 API 風格&#xff0c;可滿足不同場景的需求。 1. 模塊引入 const fs require(fs); // 回調…

LeetCode 70 爬樓梯(Java)

爬樓梯問題&#xff1a;動態規劃與斐波那契的巧妙結合 問題描述 假設你正在爬樓梯&#xff0c;需要爬 n 階才能到達樓頂。每次你可以爬 1 或 2 個臺階。求有多少種不同的方法可以爬到樓頂&#xff1f; 示例&#xff1a; n 2 → 輸出 2&#xff08;1階1階 或 2階&#xff0…

【學習分享】shell基礎-參數傳遞

參數傳遞 我們可以在執行 Shell 腳本時&#xff0c;向腳本傳遞參數&#xff0c;腳本內獲取參數的格式為 $n&#xff0c;n 代表一個數字&#xff0c;1 為執行腳本的第一個參數&#xff0c;2 為執行腳本的第二個參數。 例如可以使用 $1、$2 等來引用傳遞給腳本的參數&#xff0…

Fluence推出“Pointless計劃”:五種方式參與RWA算力資產新時代

2025年6月1日&#xff0c;去中心化算力平臺 Fluence 正式宣布啟動“Pointless 計劃”——這是其《Fluence Vision 2026》戰略中四項核心舉措之一&#xff0c;旨在通過貢獻驅動的積分體系&#xff0c;激勵更廣泛的社區參與&#xff0c;為用戶帶來現實世界資產&#xff08;RWA&am…

Excel數據分析:基礎

在現代辦公環境中&#xff0c;Excel 是一款不可或缺的工具&#xff0c;它是 Microsoft&#xff08;微軟&#xff09;開發的電子表格軟件&#xff0c;用于處理和分析結構化數據。市場上還有其他類似的軟件&#xff0c;如 Google Sheets 和 Apple Numbers&#xff0c;但 Excel 以…

12V降5V12A大功率WD5030A,充電器、便攜式設備、網絡及工業領域的理想選擇

WD5030A 高效單片同步降壓型直流 / 直流轉換器 一、芯片核心概述 WD5030A 是一款高性能同步降壓型 DC/DC 轉換器&#xff0c;采用 平均電流模式控制架構&#xff08;帶頻率抖動功能&#xff09;&#xff0c;具備以下核心優勢&#xff1a; 精準電流控制&#xff1a;快速響應負…