實驗要求:模擬 DOS 環境下常見內部命令(dir, cd, md, rd, cls, date, time, ren, copy, help, quit)的幫助信息查詢系統。用戶輸入命令名,程序輸出對應功能說明;支持 help 列出所有命令,quit 退出。本文提供字符串數組匹配與結構體命令表兩種實現方案,並分析 gets 安全隱患、輸入緩衝區處理、代碼擴展性等工程化改進點。
#include#include int main() { // 命令名數組 char *cmds[] = {"dir", "cd", "md", "rd", "cls", "date", "time", "ren", "copy", "help", "quit"}; // 對應幫助信息數組 (一一對應) char *helps[] = { "dir: 無參數,顯示當前目錄文件和文件夾", "cd 目錄名: 進入指定目錄", "md 目錄名: 創建目錄 (make directory)", "rd 目錄名: 刪除目錄 (remove directory)", "cls: 清屏", "date: 設置/顯示系統日期", "time: 設置/顯示系統時間", "ren 舊名 新名: 重命名文件", "copy 源路徑 目標路徑: 復制文件", "help: 顯示所有命令幫助", "quit: 退出程序" }; int n = sizeof(cmds) / sizeof(cmds[0]); char input[20]; while (1) { printf("請輸入命令 (help/quit): "); // 危險:gets 無緩衝區保護,已被 C11 移除,嚴禁生產使用! // gets(input); // 安全替換: if (fgets(input, sizeof(input), stdin) == NULL) break; input[strcspn(input, "\n")] = '\0'; // 移除換行 if (strcmp(input, "quit") == 0) { printf("退出程序。\n"); break; } int found = 0; for (int i = 0; i < n; i++) { if (strcmp(input, cmds[i]) == 0) { printf("%s\n", helps[i]); found = 1; break; } } if (!found && strcmp(input, "help") != 0) { printf("未知命令:%s\n", input); } if (strcmp(input, "help") == 0) { for (int i = 0; i < n; i++) printf("%s\n", helps[i]); } } return 0; }
#include#include #include typedef struct { const char *name; const char *usage; const char *desc; void (*handler)(void); // 擴展:實際執行函數 } Command; // 命令處理函數 (示例) void cmd_dir() { printf("執行 dir: 列出文件...\n"); } void cmd_help(); // 前向聲明 // 命令表:數據驅動,新增命令只需擴展此表 Command cmd_table[] = { {"dir", "dir", "顯示當前目錄文件", cmd_dir}, {"cd", "cd <目錄>", "切換目錄", NULL}, {"md", "md <目錄>", "創建目錄", NULL}, {"rd", "rd <目錄>", "刪除空目錄", NULL}, {"cls", "cls", "清屏", NULL}, {"date", "date [MM-DD-YYYY]", "設置/顯示日期", NULL}, {"time", "time [HH:MM:SS]", "設置/顯示時間", NULL}, {"ren", "ren <舊名> <新名>", "重命名", NULL}, {"copy", "copy <源> <目標>", "復制文件", NULL}, {"help", "help", "顯示幫助", NULL}, // handler 內部處理 {"quit", "quit", "退出程序", NULL} }; int cmd_count = sizeof(cmd_table) / sizeof(Command); void cmd_help() { printf("可用命令:\n"); for (int i = 0; i < cmd_count; i++) { printf(" %-8s %-20s %s\n", cmd_table[i].name, cmd_table[i].usage, cmd_table[i].desc); } } int main() { char input[64]; while (1) { printf("DOS> "); if (fgets(input, sizeof(input), stdin) == NULL) break; input[strcspn(input, "\n")] = '\0'; if (strlen(input) == 0) continue; // 簡單解析:提取第一個 token 作為命令 char *token = strtok(input, " "); if (!token) continue; int found = 0; for (int i = 0; i < cmd_count; i++) { if (strcmp(token, cmd_table[i].name) == 0) { found = 1; if (strcmp(token, "help") == 0) { cmd_help(); } else if (strcmp(token, "quit") == 0) { printf("Goodbye!\n"); return 0; } else if (cmd_table[i].handler) { cmd_table[i].handler(); // 實際執行 } else { printf("%s: %s\n", cmd_table[i].usage, cmd_table[i].desc); // 僅顯示幫助 } break; } } if (!found) { printf("'%s' 不是內部命令。輸入 help 獲取幫助。\n", token); } } return 0; }
gets`,改用 fgets`// gets(input) // 危險:無長度檢查,緩衝區溢出漏洞 fgets(input, sizeof(input), stdin); // 安全:限制讀取長度 input[strcspn(input, "\n")] = '\0'; // 手動去除換行符
strtok 分割參數,支持帶參數命令 (如 cd test)。handler 函數指針,邁向真正的命令解釋器。help:顯示所有命令摘要表 (名稱、用法、描述)。help <命令>:顯示特定命令詳細說明 (擴展點)。DOS> dir dir: 顯示當前目錄文件 DOS> cd cd <目錄>: 切換目錄 DOS> help 可用命令: dir dir 顯示當前目錄文件 cd cd <目錄> 切換目錄 md md <目錄> 創建目錄 ... quit quit 退出程序 DOS> quit Goodbye!
strcmp、循環、分支基本語法。fgets/strtok 解析 + 函數指針,具備真實解釋器雛形。cd/md/rd 實際目錄操作 (chdir/mkdir/rmdir 系統調用)、路徑解析 (相對/絕對)、環境變量 (PATH)、管道/重定向 (dup2)、腳本執行。gets/scanf("%s") 讀取不定長輸入,堅持 fgets + 手動去換行。實驗感悟:從「能跑通」到「工程化」,關鍵在於數據與邏輯分離 (命令表)、安全 API 選擇 (
fgets)、擴展點預留 (handler)。這個小實驗縮影了從寫腳本到寫系統軟件的核心思維遷徙。