域名做好了怎么做網(wǎng)站內(nèi)容對(duì)網(wǎng)絡(luò)營(yíng)銷的認(rèn)識(shí)
文章目錄
- 一、打印命令行提示符
- 二、讀取鍵盤輸入的指令
- 三、指令切割
- 四、普通命令的執(zhí)行
- 五、內(nèi)建指令執(zhí)行
- 5.1 cd指令
- 5.2 export指令
- 5.3 echo指令
- 六、結(jié)語
一、打印命令行提示符
const char* getusername() // 獲取用戶名
{return getenv("USER");
}const char* gethostname() // 獲取主機(jī)名
{return getenv("HOSTNAME");
}const char* getpwd() // 獲取當(dāng)前所處的目錄
{char* pos = strrchr(getenv("PWD"), '/'); // 查找最后一個(gè) ‘/’ if(*(pos+1) != '\0') return pos+1; // 說明不是根目錄,返回最后一個(gè)文件夾return pos;
}void tooltip() // 打印命令行提示框
{printf(LEFT "%s@%s %s" RIGHT PROMPT" ", getusername(), gethostname(), getpwd());
}
代碼分析:獲取基礎(chǔ)信息本質(zhì)上是通過調(diào)用 getenv
接口來獲取對(duì)應(yīng)環(huán)境變量的值。借助 strrchr
函數(shù)來查找當(dāng)前路徑中的最后一個(gè)文件分隔符 /
,它有可能是文件分隔符也有可能是根目錄因此要單獨(dú)判斷。
二、讀取鍵盤輸入的指令
char command[1024]; // 存儲(chǔ)鍵盤輸入的指令int getcommand(char* command, int size) // 讀取指令
{memset(command, '\0', size);char* ret = fgets(command, size, stdin); // 這里 ret 一定不為空,因?yàn)橹辽贂?huì)輸入一個(gè)回車,fgets 可以讀取回車assert(ret != NULL);(void)ret;// “假裝使用一下ret,防止有些編譯器警告”// aaabc\n\0command[strlen(command)-1] = '\0'; // 去掉結(jié)尾的 \nreturn 1;
}int interact(char* command, int size) // 交互
{tooltip();while(getcommand(command, size) && (strlen(command) == 0)){tooltip();}
}int main()
{interact(command, sizeof(command)); // 交互printf("echo: %s\n", command);return 0;
}
代碼分析:鍵盤輸入的指令本質(zhì)上就是一串字符串,這里不能用 scanf
來獲取字符串,因?yàn)?scanf
是不會(huì)讀取空格和回車的(遇到空格和回車就停止讀取),而我們一般的指令都是帶選項(xiàng)的,指令和選項(xiàng)之間一般會(huì)用空格隔開,用 scanf
會(huì)導(dǎo)致我們指令讀不全。這里使用 fgets
函數(shù)來讀取鍵盤輸入,其第一參數(shù)是存儲(chǔ)指令的空間的首地址;第二個(gè)參數(shù)是空間的大小;第三個(gè)參數(shù)是從哪個(gè)文件流中讀取,一個(gè) C/C++ 程序默認(rèn)會(huì)打開三個(gè)文件流 stdin
、stdout
、stderr
,這里選擇從 stdin
中讀取,也就是從標(biāo)準(zhǔn)輸入中讀取。gets
函數(shù)會(huì)在結(jié)尾自動(dòng)幫我們添加 \0
,并且當(dāng)讀取的字符個(gè)數(shù)大于存儲(chǔ)容量時(shí),該函數(shù)會(huì)自動(dòng)在結(jié)尾放 \0
,因此我們可以不用考慮為 \0
預(yù)留空間或者認(rèn)為的在字符串結(jié)尾加 \0
。其次該函數(shù)讀取成功返回 command
的首地址,否則返回 NULL
,在當(dāng)前場(chǎng)景下,除非讀取錯(cuò)誤,否則至少都會(huì)讀入一個(gè) \n
,一般我們輸入完指令就是敲回車,什么指令不輸也敲回車,因此正常情況下 ret
不可能為 NULL
。這里還要考慮刪除掉讀取到的 \n
,因?yàn)槲覀儾恍枰?#xff0c;我們只要完整的指令。
三、指令切割
#define SEPARATOR " " // 指令分隔符
char* argv[ARGC_LONG] = {NULL}; // 存儲(chǔ)指令和選項(xiàng)的起始地址void commandcut(char* command, char** argv, int argvsize) // 指令切割
{memset(argv, 0, argvsize); // 清空char cop_command[COMMAND_LONG] = {'\0'}; // 保證 command 串不被改變for(int i = 0; command[i] != '\0'; i++){cop_command[i] = command[i];}// 開始切割子串char* ret = strtok(cop_command, SEPARATOR);int i = 0;while(ret != NULL){argv[i++] = ret;ret = strtok(NULL, " ");}
}int main()
{while(1){// 1、交互獲取命令行參數(shù)interact(command, sizeof(command)); // 交互// 到這里說明指令已經(jīng)獲取到了,接下來將指令打散// 2、指令切割commandcut(command, argv, sizeof(argv));for(int i = 0; argv[i]; i++){printf("[%d]: %s\n", i, argv[i]);}printf("echo: %s\n", command);}return 0;
}
代碼分析:這一步主要是借助 strtok
函數(shù)將獲取到的指令切割成一個(gè)一個(gè)的子串,將所有子串的起始地址存儲(chǔ)在 argv
里面。注意 strtok
函數(shù)會(huì)改變?cè)臻g的內(nèi)容,因此創(chuàng)建了一段臨時(shí)的空間 cop_command
。
四、普通命令的執(zhí)行
void normalcommandexecution(char** _argv, int* _lastcode) // 普通命令的執(zhí)行
{pid_t id = fork();if(id < 0){perror("fork");}else if(id == 0){// childint ret = execvp(_argv[0], _argv);if(ret == -1){perror("exeecp");exit(EXIT_CODE);}}else{// fatherint status;pid_t ret = waitpid(id, &status, 0); // 阻塞等待if(ret == id){*_lastcode = WEXITSTATUS(status);}}
}int main()
{while(1){// 1、交互獲取命令行參數(shù)interact(command, sizeof(command)); // 交互// 到這里說明指令已經(jīng)獲取到了,接下來將指令打散// 2、指令切割commandcut(command, argv, sizeof(argv));// 3、普通命令執(zhí)行normalcommandexecution(argv, &lastcode);}return 0;
}
代碼分析:對(duì)于 ls
這種普通指令(非內(nèi)建指令),先通過 fork
創(chuàng)建子進(jìn)程,然后再調(diào)用 execvp
接口進(jìn)行程序替換,去執(zhí)行輸入的指令。
五、內(nèi)建指令執(zhí)行
5.1 cd指令
bool isnormalcommand(char **_argv) // 指令判斷
{if (strcmp(_argv[0], "cd") == 0)return false;return true;
}void changpwd(char** _argv) // 更改當(dāng)前工作目錄
{chdir(_argv[1]); // 更改當(dāng)前工作目錄// getpwd(pwd, sizeof(pwd));sprintf(getenv("PWD"), "%s", getcwd(pwd, sizeof(pwd))); // 修改環(huán)境變量
}void builtincommand(char **_argv) // 內(nèi)建命令執(zhí)行
{if (strcmp(_argv[0], "cd") == 0){changpwd(_argv);}
}int main()
{while (1){// 1、交互獲取命令行參數(shù)interact(command, sizeof(command)); // 交互// 到這里說明指令已經(jīng)獲取到了,接下來將指令打散// 2、指令切割commandcut(command, argv, sizeof(argv));// 3、指令判斷// 3、普通命令執(zhí)行if (isnormalcommand(argv)) // 普通指令normalcommandexecution(argv, &lastcode);else // 內(nèi)建指令builtincommand(argv);}return 0;
}
代碼分析:要考慮內(nèi)建指令,那在指令切割之后要先對(duì)指令進(jìn)行判斷。內(nèi)建指令不需要?jiǎng)?chuàng)建子進(jìn)程去執(zhí)行,而是直接由當(dāng)前的 bash
進(jìn)程去執(zhí)行。比如說 cd
指令,執(zhí)行完 cd
指令后,我們要讓當(dāng)前的 bash
更改工作目錄,而不是讓其創(chuàng)建子進(jìn)程去執(zhí)行 cd
指令,那樣改變的就是子進(jìn)程的工作目錄??梢园l(fā)現(xiàn),一個(gè)指令執(zhí)行完后,如果會(huì)對(duì) bash
產(chǎn)生影響,那么它就必須是內(nèi)建指令。其次關(guān)于 cd
指令,它改變了當(dāng)前的工作目錄,這一點(diǎn)該如何理解呢?我 myshell
就是一個(gè)可執(zhí)行程序,我的源代碼和編譯得到的可執(zhí)行文件始終都放在 /home/wcy/linux-s/2023-10-28a/myshell
目錄下,你 cd
命令憑什么能改變我的工作目錄?其實(shí)并不然,這里改變工作目錄是:一個(gè)可執(zhí)行程序在變成進(jìn)程產(chǎn)生 PCB 對(duì)象后,PCB 里面維護(hù)了一個(gè)屬性就叫做當(dāng)前可執(zhí)行程序的工作目錄,cd
指令改變的其實(shí)就是這一屬性,并不是改變 myshell
程序的存儲(chǔ)位置,我們通過調(diào)用 chdir
系統(tǒng)調(diào)用來修改這一屬性。最后,因?yàn)槲覀兦懊媸峭ㄟ^環(huán)境變量來獲取當(dāng)前工作目錄,而環(huán)境變量在被當(dāng)前 myshell
進(jìn)程從父進(jìn)程繼承下來后是不會(huì)自動(dòng)發(fā)生改變的,因此在執(zhí)行完 cd
指令后,我們要對(duì) PWD
環(huán)境變量進(jìn)行修改,環(huán)境變量本質(zhì)上就是存儲(chǔ)在內(nèi)存中的一段字符串信息,因此我們可以采用 sprintf
函數(shù)對(duì)該字符串信息進(jìn)行修改。
5.2 export指令
#define USER_ENV_SIZE 100 // 允許用戶添加的環(huán)境變量個(gè)數(shù)
#define USER_ENV_LONG 1024 // 用戶一個(gè)環(huán)境變量的最大長(zhǎng)度char userenv[USER_ENV_SIZE][USER_ENV_LONG]; // 保存用戶添加的環(huán)境變量
int userenvnum = 0; // 當(dāng)前用戶輸入的環(huán)境變量個(gè)數(shù)void exportcommand(char** _argv, char(*_userenv)[USER_ENV_LONG], int* _userenvnum)
{// 將用戶輸入的環(huán)境變量存儲(chǔ)起來strcpy(_userenv[*_userenvnum], _argv[1]);int ret = putenv(_userenv[(*_userenvnum)++]);if (ret == 0)perror("putenv");
}
代碼分析:只要 bash
不退出,我們每次添加的環(huán)境變量都應(yīng)該被保存起來,我們輸入的環(huán)境變量是被當(dāng)做指令保存在 command
里面,當(dāng)下一次輸入指令,上一次輸入的內(nèi)容就會(huì)被清空。putenv
添加環(huán)境變量,并不是把對(duì)應(yīng)的字符串拷貝到系統(tǒng)的表當(dāng)中,而是把該字符串的地址保存在系統(tǒng)的表中,因此我們要確保保存環(huán)境變量字符串的那個(gè)地址里的環(huán)境變量不會(huì)被修改,所以我們需要為用戶輸入的環(huán)境變量,也就是那一串字符串單獨(dú)開辟一塊空間進(jìn)行存儲(chǔ),保證在內(nèi)次重新輸入指令的時(shí)候,不會(huì)影響到之前用戶添加的環(huán)境變量。因?yàn)榄h(huán)境變量本質(zhì)就是一個(gè)字符串,所以這里我們定義了一個(gè)字符二維數(shù)組來存儲(chǔ)用戶輸入的環(huán)境變量,先把用戶輸入的環(huán)境變量存入我們定義的這個(gè)數(shù)組,然后再調(diào)用 putenv
函數(shù)將數(shù)組中的內(nèi)容添加到當(dāng)前的環(huán)境變量。這樣就可以保證只要當(dāng)前 bash
不退出,用戶歷史上添加的環(huán)境變量都在。這里涉及到二維數(shù)組傳參的問題,再來回顧一下,數(shù)組名表示首元素地址,二維數(shù)組的首元素是一個(gè)一維數(shù)組,所以函數(shù)形參的類型是一個(gè)字符一維數(shù)組的地址,也就是 char(*)[USER_ENV_LONG]
。
5.3 echo指令
void echocommand(char **_argv, int _argc)
{if (_argv[1][0] == '$'){char *ptr = _argv[1] + 1;printf("%s\n", getenv(ptr));}else{int i = 1;while (i < _argc){char *ret = strtok(_argv[i], "\"");while (ret != NULL){printf("%s", ret);ret = strtok(NULL, "\"");}printf("%c", ' ');i++;}printf("\n");}
}
代碼分析:echo
指令需要考慮將輸入的 "
去掉,其次可能連續(xù)輸入多個(gè)字符串,還要考慮 echo
和 $
配合使用是去打印環(huán)境變量的值。
小結(jié):當(dāng)我們登陸的時(shí)候,系統(tǒng)就是要啟動(dòng)一個(gè) shell
進(jìn)程,我們 shell
本身的環(huán)境變量是在用戶登錄的時(shí)候,shell
會(huì)讀取用戶目錄下的 .bash_profile
文件,里面保存了導(dǎo)入環(huán)境變量的方式。
六、結(jié)語
今天的分享到這里就結(jié)束啦!如果覺得文章還不錯(cuò)的話,可以三連支持一下,春人的主頁還有很多有趣的文章,歡迎小伙伴們前去點(diǎn)評(píng),您的支持就是春人前進(jìn)的動(dòng)力!