【Linux】1w詳解如何實現一個簡單的shell

?目錄?

實現思路

1. 交互 獲取命令行

2. 子串分割 解析命令行

3. 指令的判斷 內建命令

4. 普通命令的執行

?補充:vim 文本替換

整體代碼

重點思考

1.getenv和putenv是什么意思

2.代碼extern char **environ;

3.內建命令是什么

4.lastcode = WEXITSTATUS(status);

5.execvp(_argv[0], _argv);的調用

6._argc&_argv


實現思路

1. 交互 獲取命令行

顯示提示符和獲取用戶輸入

Shell本質是一個死循環,不斷地顯示提示符和獲取用戶輸入

memset?函數

memset?函數用于將一段內存區域設置為指定的值。它的原型是:

void *memset(void *s, int c, size_t n);

參數說明:

  • s:指向要填充的內存區域的指針。

  • c:要設置的值(以無符號字符形式傳遞,但實際存儲在內存中的每個字節的值是該無符號字符的值)。

  • n:要設置的字節數。

示例用法:

char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));

這里的代碼表示將?command_line?數組的每個字節都設置為?\0(空字符),確保初始化整個數組。

fgets?函數

fgets?函數用于從指定的輸入流讀取字符串。它的原型是:

char *fgets(char *s, int n, FILE *stream);

參數說明:

  • s:指向存儲讀取數據的字符數組的指針。

  • n:要讀取的最大字符數(包括終止字符?\0)。

  • stream:輸入流,通常是?stdin?用于標準輸入。

示例用法:

fgets(command_line, NUM, stdin);

這行代碼表示從標準輸入讀取最多?NUM-1?個字符(預留一個字符用于終止字符?\0)到?command_line?數組中。

綜合示例

結合起來,代碼片段如下所示:

char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));
fgets(command_line, NUM, stdin);

這段代碼的作用是:

  1. 使用?memset?函數將?command_line?數組的所有字節都設置為?\0,即初始化數組。

  2. 使用?fgets?函數從標準輸入讀取最多?NUM-1?個字符并存儲在?command_line?數組中。

這樣處理后,command_line?數組會包含從輸入讀取的字符串,并且如果字符串的長度小于?NUM,數組中剩余的字節會保持為?\0

以下是實現這兩個步驟的代碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024char command_line[NUM]; // 用來接收命令行內容int main(void) {while (1) {/* Step1:顯示提示符 */printf("[用戶@主機 當前目錄] # ");fflush(stdout);/* Step2:獲取用戶輸入 */memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);  // 從鍵盤獲取輸入command_line[strlen(command_line) - 1] = '\0'; // 消除 '\n'printf("%s\n", command_line);}
}

通過上述代碼,我們可以實現提示用戶輸入,并獲取用戶輸入。

注意點:

執行發現有空行怎么辦

?我們利用 fgets 函數從鍵盤上獲取,標準輸入 stdin,獲取到 C 風格的字符串,

注意默認會添加 \0 ,我們先把獲取到的結果 command_line 打印出來看看:

?因為 command_line 里有一個 \n,我們把它替換成 \0 即可:

?command_line[strlen(command_line) - 1] = '\0'; ?// 消除 '\0'

2. 子串分割 解析命令行

獲取用戶輸入后,我們需要將接收到的字符串拆分為命令及其參數。

將接收到的字符串拆開

通過?strtok?函數,我們可以將一個字符串按照特定的分隔符打散,依次返回子串:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void) {while (1) {/* 顯示提示符和獲取用戶輸入 */printf("[用戶@主機 當前目錄] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 將接收到的字符串拆開 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 打印拆分結果 */for (int i = 0; i < idx - 1; i++) {printf("%d : %s\n", i, command_args[i]);}}
}

通過這段代碼,我們可以將輸入的命令行字符串拆分成多個子字符串,并打印出來。

strtok?函數的原型為:

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

參數說明:

  • str:要進行分割的字符串,第一次調用時傳入要分割的字符串,后續調用時傳入 NULL 即可。

  • delim:分隔符,用于指定分割字符串的字符。

示例用法

在代碼中,使用了?strtok?函數將?command_line?字符串按照?SEP?分隔符進行切割,并將每個子字符串存儲在?command_args?數組中。

command_args[0] = strtok(command_line, SEP);
int idx = 1;

這里的代碼首先將?command_line?字符串按照?SEP?分隔符切割成子字符串,并將第一個子字符串的指針存儲在?command_args[0]?中。然后,

利用循環逐個獲取剩余的子字符串,并將它們存儲在?command_args?數組中(使用?idx?來索引)

3. 指令的判斷 內建命令

為了實現一些特定功能,如路徑切換,我們需要在Shell中實現內建命令。

內建命令:實現路徑切換

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];/* Shell 內置函數: 路徑跳轉 */
int ChangeDir(const char* new_path) {return chdir(new_path);
}int main(void) {while (1) {/* 顯示提示符和獲取用戶輸入 */printf("[用戶@主機 當前目錄] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 將接收到的字符串拆開 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 判斷并執行內建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]);continue;}/* 執行普通命令 */}
}

這段代碼通過判斷輸入的命令是否為?cd?來執行路徑切換,而無需創建子進程。

getcwd用于獲取當前工作目錄(當前目錄)的路徑。該函數的聲明如下:

char *getcwd(char *buf, size_t size);

函數參數說明:

  • buf:指向存儲當前工作目錄路徑的緩沖區
  • size:緩沖區的大小

函數返回值: 如果函數調用成功,則返回指向存儲當前工作目錄路徑的緩沖區的指針;如果函數調用失敗,則返回NULL。

通過調用getcwd函數,可以獲取當前程序所在的工作目錄路徑。


chdir用于改變當前工作目錄(當前目錄)的路徑。該函數的聲明如下:

int chdir(const char *path);

函數參數說明:

  • path:要設置為當前工作目錄的路徑

函數返回值: 如果函數調用成功,則返回0;如果函數調用失敗,則返回-1,并設置errno來指示錯誤的類型。

4. 普通命令的執行

最后,我們實現普通命令的執行,包括創建子進程并執行用戶輸入的命令。

創建進程 & 程序替換

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void)
{while (1) {/* Step1:顯示提示符 */printf("[lvy@我的主機名 當前目錄] # ");fflush(stdout);/* Step2:獲取用戶輸入 */memset (command_line, '\0', sizeof(command_line) * sizeof(char));fgets(command_line, NUM, stdin);  /* 從鍵盤獲取,標準輸入,stdin 獲取到 C 風格的字符串,默認添加 '\0' */command_line[strlen(command_line) - 1] = '\0';  // 消除 '\0'/* Step3: 將接收到的字符串拆開 - 字符串切分 */command_args[0] = strtok(command_line, SEP);int idx = 1;/* 這里的 = 是故意這么寫的,因為 strtok 截取成功返回字符串起始地址截取失敗,返回 NULL */while (command_args[idx++] = strtok(NULL, SEP));//我們來測試一下看看 // for (int i = 0; i < idx; i++) {//     printf("%d : %s\n", i, command_args[i]);// }// printf("%s\n", command_line);/* Step4. TODO *//* Step5. 創建進程,執行 */pid_t id = fork();if (id == 0) {/* child *//* Step6: 程序替換 */execvp (command_args[0],  // 保存的是我們要執行的程序名字command_args);exit(1);   // 只要執行到這里,子進程一定是替換失敗了,直接退出。}/* Father */int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0) {   // 等待成功printf("等待成功!sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);}} // end while}

通過上述代碼,我們可以創建一個進程來執行用戶輸入的命令,并等待子進程結束。

給命令帶顏色

為了增強Shell的用戶體驗,可以給一些常用命令添加顏色,例如?ls?命令:

/* 將接收到的字符串拆開 */
command_args[0] = strtok(command_line, SEP);
int idx = 1;
while ((command_args[idx++] = strtok(NULL, SEP)));/* 給 ls 命令添加顏色 */
if (strcmp(command_args[0], "ls") == 0) {command_args[idx++] = (char*)"--color=auto";
}

以上實現了一個簡單的Shell,具備了基本的提示符顯示、用戶輸入獲取、命令解析、內建命令和普通命令的執行功能。

內建命令 環境變量

/* Shell 內置函數: 路徑跳轉 */
int ChangeDir(const char* new_path) {chdir(new_path);return 0;  // 調用成功
}int main(void) 
{.../* Step4. TODO 編寫后面的邏輯,內建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]);  // 讓調用方進行路徑切換continue;}...
}

保存環境變量的字符串,不能是易變的,所以 strcpy mycommand,實現與argv的分離

?補充:vim 文本替換

如何快速將mycmd換為myshell呢

通過如下操作

: %s/mycmd/myshell/g

就可以啦


細節設置的思考,在最后一部分,讓我們先來看一下整體

整體代碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>//創建子進程
#include <stdlib.h>//這些文件都是什么意思
#include <sys/types.h>
#include <sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];//存儲切割之后的命令行
char pwd[LINE_SIZE];// 自定義環境變量表
char myenv[LINE_SIZE];
// 自定義本地變量表const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd, sizeof(pwd));//獲取當前工作目錄
}void interact(char *cline, int size)//交互
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);輸入流進行輸入assert(s);//斷言不為空(void)s;//調用s避免報錯// "abcd\n\0"cline[strlen(cline)-1] = '\0';//取消自動換行
}// ls -a -l | wc -l | head 
int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);//區分全局變量和形參_while(_argv[i++] = strtok(NULL, DELIM)); // 故意寫的=//NULL的設置才能實現往后移的切割return i - 1;//去除NULL
}void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//讓子進程執行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);//系統調用exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {//返回正確執行lastcode = WEXITSTATUS(status);}}
}
//切換路徑,內建命令
//shell執行的內建命令,一個一個判斷
int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);//切換路徑的函數getpwd();//獲取當前路徑sprintf(getenv("PWD"), "%s", pwd);return 1;}//導入環境變量exportelse if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);//為什么要進行一個拷貝 是什么意思呢!!!!putenv(myenv);//argv 是我們定義的,每次都是變化的//所以不要寫我們的地址,要提字符串的地址return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);//查看退出碼lastcode=0;}else if(*_argv[1] == '$'){//打印環境變量char *val = getenv(_argv[1]+1);//獲取if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊處理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0; //帶有顏色之后返回,再執行普通命令
}int main()
{while(!quit){//1.許多軟件啟動起來就是死循環// 2. 交互問題,獲取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtinteract(commandline, sizeof(commandline));//對函數做了一下封裝// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"// 3. 子串分割的問題,解析命令行int argc = splitstring(commandline, argv);//如何將字串打散呢//strtok需要循環調用//while(argv[i++]=strtok(commandline,DELIM);//故意寫的等號if(argc == 0) continue;// 4. 指令的判斷 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//內鍵命令,本質就是一個shell內部的一個函數int n = buildCommand(argv, argc);// ls -a -l | wc -l// 4.0 分析輸入的命令行字符串,獲取有多少個|, 命令打散多個子命令字符串// 4.1 malloc申請空間,pipe先申請多個管道// 4.2 循環創建多個子進程,每一個子進程的重定向情況。最開始. 輸出重定向, 1->指定的一個管道的寫端 // 中間:輸入輸出重定向, 0標準輸入重定向到上一個管道的讀端 1標準輸出重定向到下一個管道的寫端// 最后一個:輸入重定向,將標準輸入重定向到最后一個管道的讀端// 4.3 分別讓不同的子進程執行不同的命令--- exec* --- exec*不會影響該進程曾經打開的文件,不會影響預先設置好的管道重定向// 5. 普通命令的執行if(!n) NormalExcute(argv);//讓命令0的時候執行}return 0;
}

重點思考

1.getenv和putenv是什么意思

?getenv函數用于獲取指定環境變量的值。它的函數定義如下:

char *getenv(const char *name);
  • 參數:

    • name要獲取的環境變量的名稱。

  • 返回值:

    • 如果指定的環境變量存在,那么返回一個指向該環境變量值的指針

    • 如果指定的環境變量不存在,則返回NULL

以下是一個使用getenv函數的示例:

#include <stdio.h>
#include <stdlib.h>int main() {char *path = getenv("PATH");if (path != NULL) {printf("PATH environment variable: %s\n", path);} else {printf("PATH environment variable not found.\n");}return 0;
}

成功實現對環境變量的調用啦

putenv函數

putenv函數用于設置環境變量。它的函數定義如下:

int putenv(char *string);
  • 參數:

    • string:形式為"name=value"的字符串,用于設置具體的環境變量及其值

  • 返回值:

    • 成功時返回0。

    • 失敗時返回非零值。

以下是一個使用putenv函數的示例:

#include <stdio.h>
#include <stdlib.h>int main() {char env_str[] = "MY_ENV=hello_world";if (putenv(env_str) == 0) {printf("Environment variable set successfully.\n");} else {perror("putenv");return 1;}char *my_env = getenv("MY_ENV");if (my_env != NULL) {printf("MY_ENV: %s\n", my_env);} else {printf("MY_ENV environment variable not found.\n");}return 0;
}

注意事項

  1. 內存管理

    • getenv返回的指針指向的是環境變量的值,不能直接修改此值,否則可能導致未定義行為。

    • putenv函數參數所指向的字符串在函數調用后仍需存在,因為putenv不會復制這個字符串。因此傳遞給putenv的字符串應始終位于可修改的全局或堆內存中,而不是局部變量中。

  1. 線程安全性

    • getenvputenv函數在某些實現中不是線程安全的,特別是當修改同一個環境變量時。建議在多線程環境中使用setenvunsetenv函數,它們是現代C庫中提供的線程安全的替代函數。

總結

getenvputenv是C語言中用于獲取和設置環境變量的基本函數。通過了解并正確使用它們,可以更好地管理進程環境

2.代碼extern char **environ;

?extern char **environ; 是C語言中的全局變量聲明,用于訪問當前進程的環境變量。為了理解這一行代碼,我們需要理清以下幾個關鍵概念:

環境變量的存儲

在Unix和類Unix操作系統(如Linux)中,環境變量是一組鍵值對(例如PATH=/usr/bin),用于向進程傳遞配置信息。每個環境變量項以字符串的形式存儲在一個全局變量數組中。這個數組在進程啟動時由操作系統初始化,并且每個程序都可以訪問和修改它。

環境變量在內存中的表示

在內存中,環境變量通常表示為一個字符串數組,每個字符串保存一個環境變量。例如:

PATH=/usr/bin
HOME=/home/user
USER=user
...

這些字符串指針存儲在一個全局變量數組中,即char **environ

extern關鍵字

extern關鍵字用于聲明一個全局變量,但不定義它。它告訴編譯器這個變量是在別處(比如另一個源文件或由操作系統提供)定義的。因此,extern char **environ; 僅僅是一個聲明,用來告知編譯器這個變量在別處已經定義過,可以在當前文件中使用它。

為什么這樣寫?

在標準C庫中,environ變量實際上在系統庫中已經定義,我們只需要在我們的程序中聲明一下即可使用。這種方式使我們能夠訪問和操作環境變量

這里是extern char **environ;的具體含義:

  1. 聲明:它聲明了一個外部變量environ,是一個指向字符指針的指針。

  2. 外部定義:實際的環境變量數組由操作系統初始化,并定義在某個系統庫中。

  3. 全局訪問:通過這個聲明,我們可以在任何源文件中訪問和操作環境變量。

示例

下面是一個具體的例子,展示了如何使用environ來訪問并打印所有環境變量:

#include <stdio.h>// 聲明外部環境變量數組
extern char **environ;int main(void) {// 指向環境變量數組的指針char **env = environ;// 遍歷并打印所有環境變量while (*env) {printf("%s\n", *env);env++;}return 0;
}

就可以成功調用所有環境變量啦

總結

extern char **environ; 這一行代碼的作用是聲明一個指針數組,用于訪問當前進程的環境變量。通過這種方式,我們可以在C程序中方便地讀取和操作環境變量

3.內建命令是什么

內建命令是指直接內置在操作系統內核中的一些命令,與普通的外部命令(外部程序文件)不同。這些內建命令是直接由shell解釋器(如Bash、Zsh等)所處理,而不需要通過外部文件的方式來執行。這些內建命令通常在操作系統的shell環境中被頻繁使用,并且執行速度更快,因為它們不需要創建新的進程來執行。

在Unix和類Unix操作系統中,通常會有一些內建命令,比如cdechoexit等。這些命令不需要單獨的可執行文件,而是直接由shell內核提供支持。當用戶在shell中輸入這些命令時,shell會直接處理它們,而不需要通過搜索系統路徑來找到可執行文件。

值得一提的是,某些shell也允許用戶通過自定義的方式添加新的內建命令,這樣用戶可以根據自己的需求來擴展shell的內建功能。

4.lastcode = WEXITSTATUS(status);

在C語言中,WEXITSTATUS(status) 是一個宏,用于從waitwaitpid返回的狀態信息中提取子進程的退出狀態。這個宏主要用于處理子進程的退出狀態信息

具體來說,WEXITSTATUS(status) 用于提取子進程在終止時傳遞給exit_exit函數的退出狀態。這個宏將狀態信息進行適當的位操作,以獲取子進程的退出狀態值。

一般情況下,status 是由waitwaitpid函數返回的子進程狀態,其中包含了有關子進程終止的信息,包括退出狀態。通過使用WEXITSTATUS(status),可以將狀態轉換為子進程的退出狀態,以便于后續處理和判斷子進程的終止情況。

具體的用法示例如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid;int status;int lastcode;pid = fork();if (pid < 0) {perror("fork failed");exit(1);} else if (pid == 0) {// This is the child processchar *args[] = {"ls", "-l", NULL};execvp(args[0], args);} else {// This is the parent processwaitpid(pid, &status, 0);//獲得了子進程的退出碼lastcode = WEXITSTATUS(status);printf("子進程的退出狀態是:%d\n", lastcode);}return 0;
}

在這個例子中,WEXITSTATUS(status) 會從 status 中提取子進程的退出狀態,并將其賦值給 lastcode。然后這個退出狀態可以被用來進行一些處理,比如根據不同的退出狀態進行不同的操作。

需要注意的是,使用 WEXITSTATUS(status) 的前提是要確保傳入的 status 參數是一個子進程終止的狀態,因為該宏只能提取終止進程的退出狀態信息。

5.execvp(_argv[0], _argv);的調用

在代碼中,execvp(_argv[0], _argv) 是一個執行函數 execvp 的調用,用于執行磁盤文件上的程序。這個函數會用指定的程序文件(由 _argv[0] 指定)來覆蓋當前進程的鏡像,并且用 _argv 數組中的參數替換掉原來的程序參數

相對路徑執行指令

  1. 路徑搜索根據?PATH?環境變量execvp?會在指定路徑中查找可執行文件。
  2. 內存映射:找到可執行文件后,將其映射到當前進程地址空間。
  3. 替換鏡像:用新程序的數據、堆棧、代碼段替換當前進程的相應部分。
  4. 執行:新程序從其入口點開始執行,覆蓋原進程的代碼。

下面是對 execvp 函數調用的解釋:

  • _argv[0] 表示要執行的程序文件的路徑或名稱。如果是一個程序的名稱而沒有路徑,execvp 會在 $PATH 環境變量指定的路徑中搜索這個程序。
  • _argv 是一個以空指針結尾的字符串數組,用于傳遞給新程序的命令行參數。數組的第一個元素(_argv[0])通常是被執行的程序的名稱,隨后的元素是程序的參數。
  • 當調用 execvp 時,操作系統會加載并執行指定的程序文件,并_argv 數組中的參數來替換當前進程的參數(因為默認會在PATH中查詢,就和系統連接上了)
  • 如果 execvp 調用成功,則當前進程的鏡像將被新程序替換,并且新程序開始執行。原來的程序代碼和數據都會被新程序的代碼和數據取代。
  • 如果 execvp 調用失敗,它會返回-1,并且當前進程的狀態不會改變。

在簡單的C代碼中,execvp 函數通常與 fork 函數一起使用,例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {char *_argv[] = {"ls", "-l", "-a", NULL}; // 要執行的命令及參數組成的數組execvp(_argv[0], _argv);  // 在新的程序中執行 ls 命令// 如果執行成功,下面的代碼不會被執行perror("execvp"); // 如果 execvp 失敗,打印出錯誤信息return 1;
}

需要注意的是,execvp 在執行成功后,原進程的代碼和數據將會被新進程替換。這就意味著,如果 execvp 后面還有代碼,那么這些代碼將不會被執行,因為當前的程序已經不再存在。

實現shell,?一行一行的運行,先判斷是否為內建命令

6._argc&_argv

  • _argv:是一個字符指針數組,用于存儲命令和參數。
  • _argc:是整型變量,用于存儲命令和參數的數量
  • splitstring?函數將命令行字符串分割成多個子字符串,存儲在?_argv?中,并返回子字符串的數量?_argc
  • NormalExcute?函數使用?_argv?數組創建子進程并執行命令。
  • buildCommand?函數使用?_argv?和?_argc?處理內建命令。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44411.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44411.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44411.shtml

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

相關文章

Java-final關鍵字詳解

Java-final關鍵字詳解 一、引言 二、什么是 final 關鍵字&#xff1f; 三、final 變量 final 局部變量 final 實例變量 final 靜態變量 四、final 方法 五、final 類 六、final 關鍵字的實際應用 1. 定義常量 2. 防止方法被重寫 3. 創建不可變類 4. 優化性能 七、…

切割01串(牛客小白月賽98)

題意&#xff1a; 給三個整數n&#xff0c;l&#xff0c;r&#xff0c;和一個字符串s&#xff0c;滿足l<|c0-c1|<r就可以切成字符串a和字符串b&#xff0c;c0為字符串a左側出現0的次數&#xff0c;c1為字符串b右側出現1的次數&#xff0c;求最多切割次數 知識點&#x…

Onnx 1-深度學習-概述1

Onnx 1-深度學習-概述1 一: Onnx 概念1> Onnx 介紹2> Onnx 的作用3> Onnx 應用場景4> Onnx 文件格式1. Protobuf 特點2. onnx.proto3協議3> Onnx 模型基本操作二:Onnx API1> 算子詳解2> Onnx 算子介紹三: Onnx 模型1> Onnx 函數功能

昇思學習打卡-8-計算機視覺/FCN圖像語義分割

目錄 FCN介紹FCN所用的技術訓練數據的可視化模型訓練模型推理FCN的優點和不足優點不足 FCN介紹 FCN主要用于圖像分割領域&#xff0c;是一種端到端的分割方法&#xff0c;是深度學習應用在圖像語義分割的開山之作。通過進行像素級的預測直接得出與原圖大小相等的label map。因…

【C++基礎】初識C++(2)--引用、const、inline、nullptr

目錄 一、引用 1.1 引用的概念和定義 1.2 引用的特性 1.3引用的使用 1.4 const引用 1.5 指針和引用的關系 二、inline 三、nullptr 一、引用 1.1 引用的概念和定義 引?不是新定義?個變量&#xff0c;?是給已存在變量取了?個別名&#xff0c;編譯器不會為引?…

微軟的人工智能語音生成器在測試中達到與人類同等水平

微軟公司開發了一種新的神經編解碼語言模型 Vall-E&#xff0c;在自然度、語音魯棒性和說話者相似性方面都超越了以前的成果。它是同類產品中第一個在兩個流行基準測試中達到人類同等水平的產品&#xff0c;而且顯然非常逼真&#xff0c;以至于微軟不打算向公眾開放。 VALL-E …

Node.js 模塊系統

Node.js 模塊系統 Node.js 的模塊系統是其核心特性之一,它允許開發者將代碼組織成可重用的模塊。這種系統促進了代碼的模塊化,使得大型應用程序的構建和管理變得更加容易。本文將深入探討 Node.js 的模塊系統,包括其工作原理、如何創建和使用模塊,以及模塊系統的優勢和局限…

【每日一練】python類和對象現實舉例詳細講解

""" 本節課程目的&#xff1a; 1.掌握類描述現實世界實物思想 2.掌握類和對象的關系 3.理解什么事面向對象 """ #比如設計一個鬧鐘&#xff0c;在這里就新建一個類 class Clock:idNone #鬧鐘的序列號&#xff0c;也就是類的屬性priceNone #鬧…

Git最常用操作速查表

Git常用操作 文章目錄 Git常用操作1. 克隆/拉取2. 分支操作1. 查看分支2. 創建分支3. 切換到分支4. 刪除分支5. 刪除遠程分支6. 推送分支到遠程 3. 暫存庫操作4. Git團隊規范1. 原則2. 分支設計3. commit備注一般規范 1. 克隆/拉取 git clone xxx 從遠程倉庫克隆 git rebase…

【開源之美】:WinMerge Files

一、引言 強大的windows端文件比較工具&#xff0c;跟Beyond Compare相比&#xff0c;更為強大。但是這里我們推薦他的原因&#xff0c;不僅是因為作為一個使用的工具&#xff0c;主要是因為他開源&#xff0c;可以通過調試優秀的源代碼&#xff0c;進一步的提升C項目設計和編…

Alternative to Receptive field in Transformers and what factors impact it

題意&#xff1a;Transformer中感受野的替代概念及其影響因素 問題背景&#xff1a; I have two transformer networks. One with 3 heads per attention and 15 layers in total and second one with 5 heads per layer and 30 layers in total. Given an arbitrary set of d…

什么是數據模型?數據模型與數據治理有什么關系?

在企業數據治理的廣闊領域中&#xff0c;首要且關鍵的一步是明確溝通數據治理的需求。這包括對企業所持有的數據種類、數據存儲位置、以及當前數據管理的具體情況有一個清晰的了解和記錄。了解企業的數據資產是制定有效數據治理策略的基礎。企業需要識別和盤點所有類型的數據資…

AIGC產品經理學習路徑

基礎篇&#xff08;課時 2 &#xff09; AIGC 行業視角 AIGC 的行業發展演進&#xff1a;傳統模型/深度學習/大模型 AIGC 的產品設計演進&#xff1a;AI Embedded / AI Copilot / AI Agen AIGC 的行業產業全景圖 AIGC 的產品應用全景圖 AIGC 職業視角 AI 產品經理/ AIGC…

2974.最小數字游戲

1.題目描述 你有一個下標從 0 開始、長度為 偶數 的整數數組 nums &#xff0c;同時還有一個空數組 arr 。Alice 和 Bob 決定玩一個游戲&#xff0c;游戲中每一輪 Alice 和 Bob 都會各自執行一次操作。游戲規則如下&#xff1a; 每一輪&#xff0c;Alice 先從 nums 中移除一個 …

Spring MVC 全面指南:從入門到精通的詳細解析

引言&#xff1a; Spring MVC&#xff0c;作為Spring框架的一個重要模塊&#xff0c;為構建Web應用提供了強大的功能和靈活性。無論是初學者還是有一定經驗的開發者&#xff0c;掌握Spring MVC都將顯著提升你的Web開發技能。本文旨在為初學者提供一個全面且易于理解的學習路徑…

數據建設實踐之大數據平臺(五)安裝hive

安裝hive 上傳安裝包到/opt/software目錄并解壓 [bigdata@node101 software]$ tar -zxvf hive-3.1.3-with-spark-3.3.1.tar.gz -C /opt/services [bigdata@node101 services]$ mv apache-hive-3.1.3-bin apache-hive-3.1.3 配置環境變量 export JAVA_HOME=/opt/services…

Debezium系列之:驗證mysql、mariadb等兼容mysql協議數據庫賬號權限

Debezium系列之:驗證mysql、mariadb等兼容mysql協議數據庫賬號權限 一、數據庫需要開啟binlog二、創建賬號和賬號需要賦予的權限三、賬號具有權限查看日志信息四、驗證賬號權限五、驗證賬號能否執行show master status六、驗證數據庫是否開啟binlog一、數據庫需要開啟binlog …

實驗9 存儲過程與函數的創建管理實驗

一、實驗目的&#xff1a; 理解存儲過程和函數的概念。掌握創建存儲過程和函數的方法。掌握執行存儲過程和函數的方法。掌握游標的定義、使用方法。 二、實驗內容 1&#xff0e;某超市的食品管理的數據庫的Food表&#xff0c;Food表的定義如表所示&#xff0c; Food表的定義…

【進階篇-Day8:JAVA中遞歸、異常的介紹】

目錄 1、遞歸的介紹和使用1.1 遞歸的介紹1.2 案例案例一&#xff1a;案例二&#xff1a;案例三&#xff1a;案例四&#xff1a; 1.3 總結 2、異常的介紹和使用2.1 異常的介紹&#xff1a;&#xff08;1&#xff09;能夠看懂異常&#xff08;2&#xff09;異常的體系接口和分類&…

Go語言map并發安全,互斥鎖和讀寫鎖誰更優?

并發編程是 Go 語言的一大特色&#xff0c;合理地使用鎖對于保證數據一致性和提高程序性能至關重要。 在處理并發控制時&#xff0c;sync.Mutex&#xff08;互斥鎖&#xff09;和 sync.RWMutex&#xff08;讀寫鎖&#xff09;是兩個常用的工具。理解它們各自的優劣及擅長的場景…