?目錄?
實現思路
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
?函數用于從指定的輸入流讀取字符串。它的原型是:
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);
這段代碼的作用是:
使用?
memset
?函數將?command_line
?數組的所有字節都設置為?\0
,即初始化數組。使用?
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;
}
注意事項
-
內存管理:
-
-
getenv
返回的指針指向的是環境變量的值,不能直接修改此值,否則可能導致未定義行為。 -
putenv
函數參數所指向的字符串在函數調用后仍需存在,因為putenv
不會復制這個字符串。因此傳遞給putenv
的字符串應始終位于可修改的全局或堆內存中,而不是局部變量中。
-
-
線程安全性:
-
-
getenv
和putenv
函數在某些實現中不是線程安全的,特別是當修改同一個環境變量時。建議在多線程環境中使用setenv
和unsetenv
函數,它們是現代C庫中提供的線程安全的替代函數。
-
總結
getenv
和putenv
是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;
的具體含義:
-
聲明:它聲明了一個外部變量
environ
,是一個指向字符指針的指針。 -
外部定義:實際的環境變量數組由操作系統初始化,并定義在某個系統庫中。
-
全局訪問:通過這個聲明,我們可以在任何源文件中訪問和操作環境變量。
示例
下面是一個具體的例子,展示了如何使用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操作系統中,通常會有一些內建命令,比如cd
、echo
、exit
等。這些命令不需要單獨的可執行文件,而是直接由shell內核提供支持。當用戶在shell中輸入這些命令時,shell會直接處理它們,而不需要通過搜索系統路徑來找到可執行文件。
值得一提的是,某些shell也允許用戶通過自定義的方式添加新的內建命令,這樣用戶可以根據自己的需求來擴展shell的內建功能。
4.lastcode = WEXITSTATUS(status);
在C語言中,WEXITSTATUS(status)
是一個宏,用于從wait
或waitpid
返回的狀態信息中提取子進程的退出狀態。這個宏主要用于處理子進程的退出狀態信息。
具體來說,WEXITSTATUS(status)
用于提取子進程在終止時傳遞給exit
或_exit
函數的退出狀態。這個宏將狀態信息進行適當的位操作,以獲取子進程的退出狀態值。
一般情況下,status
是由wait
或waitpid
函數返回的子進程狀態,其中包含了有關子進程終止的信息,包括退出狀態。通過使用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
數組中的參數替換掉原來的程序參數。
相對路徑執行指令
- 路徑搜索:根據?
PATH
?環境變量,execvp
?會在指定路徑中查找可執行文件。 - 內存映射:找到可執行文件后,將其映射到當前進程地址空間。
- 替換鏡像:用新程序的數據、堆棧、代碼段替換當前進程的相應部分。
- 執行:新程序從其入口點開始執行,覆蓋原進程的代碼。
下面是對 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
?處理內建命令。