首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言實驗一:DOS 命令解釋器模擬實現——字符串匹配、菜單驅動與幫助系統

     編輯:關於C語言

DOS 內部命令幫助系統:從需求分析到菜單驅動程序的完整遷徙

實驗要求:模擬 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;
}

三、 關鍵工程化改進點

1. 棄用 gets`,改用 fgets`

// gets(input)  // 危險:無長度檢查,緩衝區溢出漏洞
fgets(input, sizeof(input), stdin); // 安全:限制讀取長度
input[strcspn(input, "\n")] = '\0'; // 手動去除換行符

2. 輸入解析健壯性

  • strtok 分割參數,支持帶參數命令 (如 cd test)。
  • 忽略前導/尾隨空格,空行繼續循環。

3. 數據驅動設計 (命令表)

  • 命令屬性 (名稱、用法、描述、處理函數) 集中定義在結構體數組。
  • 新增命令只需在表中添加一行,無需修改主循環邏輯,符合開閉原則。
  • 為實際命令執行預留 handler 函數指針,邁向真正的命令解釋器。

4. 幫助系統分級

  • help:顯示所有命令摘要表 (名稱、用法、描述)。
  • help <命令>:顯示特定命令詳細說明 (擴展點)。

四、 測試用例與運行示例

DOS> dir
dir: 顯示當前目錄文件
DOS> cd
cd <目錄>: 切換目錄
DOS> help
可用命令:
dir        dir                    顯示當前目錄文件
cd         cd <目錄>              切換目錄
md         md <目錄>              創建目錄
...
quit       quit                   退出程序
DOS> quit
Goodbye!

五、 總結與擴展方向

  1. 基礎版:字符串數組匹配,適合理解 strcmp、循環、分支基本語法。
  2. 進階版:結構體命令表 + fgets/strtok 解析 + 函數指針,具備真實解釋器雛形。
  3. 擴展:實現 cd/md/rd 實際目錄操作 (chdir/mkdir/rmdir 系統調用)、路徑解析 (相對/絕對)、環境變量 (PATH)、管道/重定向 (dup2)、腳本執行。
  4. 安全:嚴禁 gets/scanf("%s") 讀取不定長輸入,堅持 fgets + 手動去換行。

實驗感悟:從「能跑通」到「工程化」,關鍵在於數據與邏輯分離 (命令表)、安全 API 選擇 (fgets)、擴展點預留 (handler)。這個小實驗縮影了從寫腳本到寫系統軟件的核心思維遷徙。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved