Linux Shell 本質是「命令解析器」:讀取用戶輸入 → 解析命令與參數 → **內建命令**直接執行、**外部命令** fork+exec 執行。本文實現一個極簡 Shell (MyShell),支援 cd 內建命令、任意外部命令執行、提示符顯示(用戶@主機:路徑$/#),代碼精簡易懂,適合學習進程控制與命令行解析。
while (1) {
print_prompt(); // 顯示 user@host:path$
read_command(); // 讀取一行
parse_command(); // 分割 argv[]
if (builtin) exec_builtin(); // cd 等
else {
pid = fork();
if (pid == 0) execvp(cmd, argv); // 子進程執行外部程式
else wait(&status); // 父進程等待
}
}
#include
#include
#include
#include
#include
#define MAXSIZE 100
void info_print() { // 顯示提示符
char buf[MAXSIZE], *username, hostname[MAXSIZE], pwd[MAXSIZE];
char ch;
username = getenv("USER");
getcwd(pwd, MAXSIZE);
gethostname(hostname, MAXSIZE);
ch = (strcmp(username, "root") == 0) ? '#' : '$';
sprintf(buf, "%s@%s:%s%c ", username, hostname, pwd, ch);
fprintf(stdout, "%s", buf);
}
int main() {
while (1) {
char command[MAXSIZE];
pid_t pid; int status;
info_print();
memset(command, 0, MAXSIZE);
fgets(command, MAXSIZE, stdin); // 比 gets 安全
command[strcspn(command, "\n")] = 0; // 去換行
if ((pid = fork()) < 0) { perror("fork"); exit(1); }
if (pid > 0) { // 父進程
wait(&status);
} else { // 子進程
// 簡易解析:按空格分割 argv
char *argv[10]; int argc = 0;
char *token = strtok(command, " ");
while (token && argc < 10) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = NULL;
if (argc == 0) exit(0); // 空命令
// 內建命令 cd
if (strcmp(argv[0], "cd") == 0) {
if (argc < 2) fprintf(stderr, "cd: missing argument\n");
else if (chdir(argv[1]) < 0) perror("chdir");
exit(0);
}
// 外部命令
if (execvp(argv[0], argv) < 0) {
fprintf(stderr, "%s: command not found\n", argv[0]);
exit(1);
}
}
}
return 0;
}
| 技術點 | 實現細節 |
|---|---|
| 命令讀取 | fgets 替代不安全的 gets,手動去換行 |
| 命令解析 | strtok 按空格分割,構建 argv[],以 NULL 結尾 |
| 內建命令 | cd 必須在 Shell 進程內執行(改變進程 CWD),不能 fork |
| 外部命令 | fork() 創建子進程 → execvp() 替換映像,搜尋 PATH |
| 父進程等待 | wait(&status) 回收子進程,避免殭屍進程 |
| 提示符 | getenv("USER"), getcwd(), gethostname() 組合 |