【硬核揭秘】Linux與C高級編程:從入門到精通,你的全棧之路!
第一部分:初識Linux與環境搭建,玩轉軟件包管理——嵌入式開發的第一道“坎”
嘿,各位C語言的“卷王”們!
你可能已經習慣了在Windows或macOS上敲代碼,用IDE點點鼠標就能編譯運行。但當你踏入嵌入式開發的大門,尤其是涉及到那些跑著Linux系統的“大家伙”(比如樹莓派、工控機、智能路由器),你就會發現,一個全新的世界在你面前展開——那就是Linux命令行!
是不是一聽到“命令行”就頭大?一堆黑底白字的字符,感覺像回到了上古時代?別急!今天,咱們就來徹底“破冰”,讓你愛上Linux,讓它成為你嵌入式開發中最得力的“助手”!
本篇是“Linux與C高級編程”系列的第一部分,我們將帶你:
初識Linux: 了解它在嵌入式領域的“江湖地位”。
環境搭建: 手把手教你如何在自己的電腦上搭建Linux開發環境。
基本命令: 掌握Linux命令行最常用的“十八般武藝”,讓你在文件系統中自由穿梭。
軟件包管理: 學會如何像“魔法師”一樣,輕松安裝、更新、卸載軟件。
準備好了嗎?咱們這就開始,讓你的Linux之旅,從“小白”變“高手”!
1.1 Linux:嵌入式世界的“無冕之王”
你可能知道Linux是服務器領域的老大,但它在嵌入式領域,更是當之無愧的“無冕之王”!
1.1.1 為什么嵌入式系統青睞Linux?
開源免費: 這是一個巨大的優勢!不需要昂貴的授權費用,大大降低了產品成本。這對于追求成本效益的嵌入式產品來說,簡直是“天賜良機”。
穩定可靠: Linux內核經過全球無數開發者多年的打磨和驗證,非常穩定,適合7x24小時不間斷運行的嵌入式設備。
強大的網絡功能: 內置完整的TCP/IP協議棧,方便實現各種網絡通信(Wi-Fi、以太網、4G/5G),這在物聯網(IoT)時代尤為重要。
豐富的驅動支持: 龐大的開源社區為各種硬件提供了大量的驅動程序,大大縮短了硬件適配的周期。
多任務與多用戶: 支持多任務并發執行,可以同時運行多個應用程序;支持多用戶,方便團隊協作和權限管理。
強大的開發工具鏈: 擁有GCC、GDB、Make等一系列成熟的開發工具,以及各種腳本語言(Shell、Python),為開發提供了極大的便利。
可裁剪性: Linux內核可以根據嵌入式設備的資源限制和功能需求進行高度裁剪,只保留必要的功能,減小系統體積和內存占用。
生態系統完善: 擁有龐大的開發者社區,遇到問題很容易找到解決方案;各種開源庫和框架豐富,可以快速構建應用。
思維導圖:Linux在嵌入式領域的優勢
graph TDA[Linux在嵌入式中的優勢] --> B[開源免費]A --> C[穩定可靠]A --> D[強大的網絡功能]A --> E[豐富的驅動支持]A --> F[多任務與多用戶]A --> G[強大的開發工具鏈]A --> H[可裁剪性]A --> I[生態系統完善]
1.1.2 常見的Linux發行版:你的“座駕”選哪個?
Linux本身只是一個內核,我們日常使用的Linux系統是基于Linux內核和各種應用軟件、工具、桌面環境等打包而成的,這被稱為發行版(Distribution)。
發行版名稱 | 特點 | 優勢 | 劣勢 | 典型應用場景 |
---|---|---|---|---|
Ubuntu | 最流行的桌面發行版,用戶友好,社區活躍。 | 易于上手,資料豐富,軟件更新快。 | 桌面環境占用資源較多,服務器版本較穩定。 | 桌面開發、服務器、學習入門、樹莓派(Ubuntu Core/Server) |
Debian | Ubuntu的“老大哥”,極其穩定,軟件包豐富。 | 穩定性極高,軟件包管理系統(APT)強大。 | 更新周期較長,部分軟件版本可能較舊。 | 服務器、嵌入式設備(如路由器、NAS)、對穩定性要求高的場景 |
CentOS/RHEL | 紅帽企業版Linux的社區版,企業級應用廣泛。 | 穩定性高,安全性好,企業級支持。 | 軟件包更新較慢,桌面環境不如Ubuntu友好。 | 服務器、企業級應用、工業控制、云平臺 |
Fedora | 紅帽的“試驗田”,技術更新快,桌面體驗好。 | 軟件版本新,技術前沿,社區活躍。 | 穩定性相對較低,更新頻繁。 | 桌面開發、技術嘗鮮、服務器(新特性) |
Arch Linux | 滾動更新,極簡主義,高度定制。 | 軟件包最新,高度自由,適合高級用戶。 | 安裝和配置復雜,需要用戶有較強的Linux基礎。 | 高級用戶、追求極致定制和性能的場景 |
OpenWrt | 專門為路由器等嵌入式設備設計的Linux發行版。 | 極度輕量級,高度可定制,強大的網絡功能。 | 學習曲線陡峭,主要面向網絡設備。 | 智能路由器、物聯網網關、網絡設備 |
Buildroot/Yocto | 嵌入式Linux構建系統,而非傳統發行版。 | 高度可裁剪,可從源碼構建完整的嵌入式Linux系統。 | 復雜,需要深入理解Linux內核和構建過程。 | 工業嵌入式設備、定制化程度高的產品 |
建議:
入門學習: 強烈推薦Ubuntu。它用戶友好,資料多,社區活躍,遇到問題容易找到答案。
嵌入式開發: 在實際項目中,你可能會遇到基于Debian、Buildroot或Yocto構建的定制Linux系統。但學習階段,Ubuntu是最好的起點。
1.2 Linux開發環境搭建:你的“練武場”
有了Linux的認知,接下來就是搭建你的“練武場”了!
1.2.1 虛擬機(VMware / VirtualBox):最穩妥的選擇
原理: 在你的Windows或macOS系統上,安裝一個虛擬機軟件(如VMware Workstation Pro/Player或VirtualBox),然后在虛擬機中安裝一個完整的Linux操作系統。
優點:
安全隔離: Linux系統與你的宿主系統完全隔離,互不影響。
方便管理: 可以隨時創建快照、克隆、備份虛擬機,方便實驗和恢復。
真實環境: 模擬了一個完整的Linux系統,與實際部署環境接近。
缺點:
占用宿主系統資源(CPU、內存、硬盤)。
性能略低于原生系統。
推薦: VMware Workstation Player(免費版)或 VirtualBox(完全免費開源)。
搭建步驟(以VirtualBox安裝Ubuntu為例):
下載VirtualBox: 訪問VirtualBox官網下載并安裝最新版本。
下載Ubuntu鏡像: 訪問Ubuntu官網下載Ubuntu Desktop的ISO鏡像文件。
創建虛擬機:
打開VirtualBox,點擊“新建”。
輸入虛擬機名稱(如“Ubuntu_Dev”),選擇Linux,Ubuntu (64-bit)。
分配內存(建議4GB以上,取決于你的宿主機內存)。
創建虛擬硬盤(建議20GB以上,動態分配)。
安裝Ubuntu:
啟動虛擬機。
在虛擬機窗口中,選擇你下載的Ubuntu ISO鏡像文件作為啟動盤。
按照Ubuntu安裝向導提示進行安裝(選擇語言、時區、鍵盤布局、創建用戶、安裝類型選擇“清除整個磁盤并安裝Ubuntu”)。
安裝增強功能(Guest Additions):
Ubuntu安裝完成后,登錄系統。
在VirtualBox菜單欄中,選擇“設備”->“安裝增強功能”。
在Ubuntu中打開光盤,運行
VBoxLinuxAdditions.run
腳本。安裝完成后重啟虛擬機。
作用: 增強功能可以實現宿主機和虛擬機之間的文件共享、剪貼板共享、鼠標集成、更好的顯示分辨率等。
1.2.2 WSL(Windows Subsystem for Linux):Windows下的“Linux子系統”
原理: WSL是微軟在Windows 10/11中提供的一個功能,它允許你在Windows上直接運行一個精簡版的Linux發行版(如Ubuntu、Debian),而無需虛擬機。
優點:
輕量級: 資源占用少,啟動速度快。
集成度高: 可以直接訪問Windows文件系統,與Windows工具無縫集成。
性能接近原生: 對于文件I/O和CPU密集型任務,性能比虛擬機更好。
缺點:
不是完整的Linux內核,某些底層操作(如直接訪問硬件)受限。
圖形界面支持不如虛擬機完善(WSL2有改進)。
推薦: 如果你主要在Windows上開發,WSL是快速搭建Linux開發環境的絕佳選擇。
搭建步驟(以WSL2安裝Ubuntu為例):
啟用WSL功能:
打開PowerShell(以管理員身份運行)。
運行命令:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
運行命令:
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
重啟電腦。
下載并安裝WSL2 Linux內核更新包: 訪問微軟官網下載并安裝最新版WSL2 Linux內核更新包。
設置WSL2為默認版本:
打開PowerShell。
運行命令:
wsl --set-default-version 2
從Microsoft Store安裝Linux發行版:
打開Microsoft Store,搜索“Ubuntu”,選擇“Ubuntu 22.04 LTS”并安裝。
安裝完成后,啟動Ubuntu應用,按照提示創建用戶名和密碼。
更新和升級:
在Ubuntu終端中運行:
sudo apt update
和sudo apt upgrade
。
1.2.3 云服務器:隨時隨地的“遠程工作站”
原理: 在阿里云、騰訊云、AWS、Google Cloud等云服務商租用一臺Linux服務器。
優點:
隨時隨地訪問: 只要有網絡,就可以通過SSH遠程連接。
配置靈活: 可以根據需求選擇不同的CPU、內存、帶寬配置。
無需本地資源: 不占用本地電腦資源。
缺點:
需要付費。
網絡延遲可能影響體驗。
推薦: 如果你需要強大的計算能力、穩定的公網IP,或者希望在不同設備上無縫切換開發環境,云服務器是很好的選擇。
1.3 Linux Shell命令:你的“武功秘籍”
Linux的魅力,很大一部分在于它的命令行界面(CLI)。學會使用Shell命令,就像掌握了一套“武功秘籍”,可以高效地完成各種任務。
1.3.1 文件和目錄操作:在文件系統中“穿梭自如”
命令 | 英文全稱/含義 | 作用 | 常用參數/示例 | 備注 |
---|---|---|---|---|
| list | 列出目錄內容 |
| 嵌入式中常用于查看文件和目錄結構。 |
| change directory | 切換當前工作目錄 |
| 路徑是Linux文件系統的核心。 |
| print working directory | 顯示當前工作目錄的絕對路徑 |
| 迷路時用來定位。 |
| make directory | 創建新目錄 |
|
|
| remove | 刪除文件或目錄 |
|
|
| copy | 復制文件或目錄 |
|
|
| move | 移動文件或目錄,或重命名文件/目錄 |
| 移動和重命名是同一個命令。 |
| touch | 創建空文件,或更新文件時間戳 |
| 快速創建文件,或用于Makefile的時間戳更新。 |
| concatenate | 連接文件并打印到標準輸出(常用于查看文件內容) |
| 適合查看小文件內容。 |
| less | 分頁查看文件內容 |
| 適合查看大文件,可向上/向下翻頁。 |
| head | 顯示文件開頭幾行(默認10行) |
| 快速預覽文件內容。 |
| tail | 顯示文件末尾幾行(默認10行) |
|
|
C語言模擬:一個簡單的Linux文件系統導航器
為了讓你更直觀地理解這些命令背后的邏輯,我們用C語言來模擬一個簡化的Linux文件系統。這個模擬器可以執行ls
, cd
, mkdir
, rm
, cp
, mv
等命令,但操作的是我們C程序內部維護的數據結構,而不是真正的文件系統。
這個模擬器將包含以下核心組件:
文件/目錄結構體: 定義文件和目錄的屬性(名稱、類型、內容/子節點)。
文件系統樹: 使用鏈表或數組來模擬目錄的層級結構。
命令解析器: 解析用戶輸入的命令字符串。
命令執行函數: 實現每個命令的具體邏輯。
這個C語言模擬器會比較龐大,但它能讓你從C代碼層面理解文件系統操作的本質。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h> // 用于模擬時間戳// --- 宏定義:文件系統限制 ---
#define MAX_NAME_LEN 64 // 文件/目錄名最大長度
#define MAX_CHILDREN 10 // 每個目錄最大子節點數
#define MAX_PATH_LEN 256 // 路徑最大長度
#define MAX_FILE_CONTENT_LEN 1024 // 文件內容最大長度// --- 枚舉:節點類型 ---
typedef enum {NODE_TYPE_DIR, // 目錄NODE_TYPE_FILE // 文件
} NodeType;// --- 結構體:文件/目錄節點 ---
typedef struct FileSystemNode {char name[MAX_NAME_LEN]; // 節點名稱NodeType type; // 節點類型 (目錄或文件)time_t creation_time; // 創建時間time_t modification_time; // 修改時間struct FileSystemNode* parent; // 父節點指針union {// 如果是目錄,存儲子節點struct {struct FileSystemNode* children[MAX_CHILDREN];int num_children;} dir;// 如果是文件,存儲文件內容struct {char content[MAX_FILE_CONTENT_LEN];int content_len;} file;} data;
} FileSystemNode;// --- 全局變量:模擬文件系統根目錄和當前工作目錄 ---
FileSystemNode* root_dir = NULL;
FileSystemNode* current_dir = NULL;// --- 輔助函數:創建新節點 ---
FileSystemNode* create_node(const char* name, NodeType type, FileSystemNode* parent) {FileSystemNode* new_node = (FileSystemNode*)malloc(sizeof(FileSystemNode));if (new_node == NULL) {fprintf(stderr, "內存分配失敗!\n");exit(EXIT_FAILURE);}strncpy(new_node->name, name, MAX_NAME_LEN - 1);new_node->name[MAX_NAME_LEN - 1] = '\0';new_node->type = type;new_node->creation_time = time(NULL);new_node->modification_time = time(NULL);new_node->parent = parent;if (type == NODE_TYPE_DIR) {new_node->data.dir.num_children = 0;for (int i = 0; i < MAX_CHILDREN; i++) {new_node->data.dir.children[i] = NULL;}} else { // NODE_TYPE_FILEnew_node->data.file.content[0] = '\0';new_node->data.file.content_len = 0;}return new_node;
}// --- 輔助函數:查找子節點 ---
FileSystemNode* find_child(FileSystemNode* parent, const char* name) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return NULL;for (int i = 0; i < parent->data.dir.num_children; i++) {if (strcmp(parent->data.dir.children[i]->name, name) == 0) {return parent->data.dir.children[i];}}return NULL;
}// --- 輔助函數:添加子節點 ---
bool add_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return false;if (parent->data.dir.num_children >= MAX_CHILDREN) {fprintf(stderr, "目錄 %s 子節點已滿。\n", parent->name);return false;}parent->data.dir.children[parent->data.dir.num_children++] = child;child->parent = parent; // 確保子節點的父指針正確parent->modification_time = time(NULL); // 更新父目錄修改時間return true;
}// --- 輔助函數:移除子節點 ---
bool remove_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR || child == NULL) return false;for (int i = 0; i < parent->data.dir.num_children; i++) {if (parent->data.dir.children[i] == child) {// 移動最后一個子節點到當前位置,然后減少子節點計數parent->data.dir.children[i] = parent->data.dir.children[parent->data.dir.num_children - 1];parent->data.dir.children[parent->data.dir.num_children - 1] = NULL;parent->data.dir.num_children--;parent->modification_time = time(NULL);return true;}}return false;
}// --- 輔助函數:遞歸釋放文件系統節點內存 ---
void free_node(FileSystemNode* node) {if (node == NULL) return;if (node->type == NODE_TYPE_DIR) {for (int i = 0; i < node->data.dir.num_children; i++) {free_node(node->data.dir.children[i]);}}free(node);
}// --- 命令實現:ls ---
void cmd_ls(FileSystemNode* dir, bool show_all, bool long_format) {if (dir == NULL || dir->type != NODE_TYPE_DIR) {printf("ls: 無法訪問目錄。\n");return;}printf("目錄 '%s' 的內容:\n", dir->name);for (int i = 0; i < dir->data.dir.num_children; i++) {FileSystemNode* child = dir->data.dir.children[i];if (child == NULL) continue;if (!show_all && child->name[0] == '.') { // 不顯示隱藏文件continue;}if (long_format) {char time_str[20];strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", localtime(&child->modification_time));printf("%s\t%s\t%s\t%s\n",(child->type == NODE_TYPE_DIR) ? "d" : "-","user", // 模擬用戶"group", // 模擬組time_str,child->name);} else {printf("%s\n", child->name);}}
}// --- 命令實現:pwd ---
void cmd_pwd(FileSystemNode* node) {if (node == NULL) {printf("/\n"); // 根目錄return;}char path[MAX_PATH_LEN];path[0] = '\0';FileSystemNode* temp = node;while (temp != NULL) {char current_segment[MAX_NAME_LEN + 1];snprintf(current_segment, sizeof(current_segment), "/%s", temp->name);strncat(current_segment, path, MAX_PATH_LEN - strlen(current_segment) - 1); // 將path拼接到current_segment后面strncpy(path, current_segment, MAX_PATH_LEN - 1); // 修正:將current_segment復制到pathpath[MAX_PATH_LEN - 1] = '\0';temp = temp->parent;}if (path[0] == '\0') { // 如果是根目錄printf("/\n");} else {printf("%s\n", path);}
}// --- 命令實現:cd ---
void cmd_cd(const char* path) {if (strcmp(path, "/") == 0) {current_dir = root_dir;printf("cd: 已切換到根目錄。\n");return;}if (strcmp(path, "..") == 0) {if (current_dir->parent != NULL) {current_dir = current_dir->parent;printf("cd: 已切換到上級目錄。\n");} else {printf("cd: 已經是根目錄。\n");}return;}if (strcmp(path, ".") == 0) {printf("cd: 仍在當前目錄。\n");return;}FileSystemNode* target_dir = find_child(current_dir, path);if (target_dir != NULL && target_dir->type == NODE_TYPE_DIR) {current_dir = target_dir;printf("cd: 已切換到目錄 '%s'。\n", path);} else {printf("cd: 目錄 '%s' 不存在或不是目錄。\n", path);}
}// --- 命令實現:mkdir ---
void cmd_mkdir(const char* name) {if (find_child(current_dir, name) != NULL) {printf("mkdir: 目錄 '%s' 已存在。\n", name);return;}FileSystemNode* new_dir = create_node(name, NODE_TYPE_DIR, current_dir);if (add_child(current_dir, new_dir)) {printf("mkdir: 目錄 '%s' 創建成功。\n", name);} else {free_node(new_dir); // 創建失敗則釋放}
}// --- 命令實現:rm ---
void cmd_rm(const char* name, bool recursive) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("rm: '%s' 不存在。\n", name);return;}if (target->type == NODE_TYPE_DIR && !recursive) {printf("rm: 無法刪除目錄 '%s',請使用 -r 選項。\n", name);return;}if (target->type == NODE_TYPE_DIR && target->data.dir.num_children > 0 && recursive) {printf("rm: 正在遞歸刪除目錄 '%s'...\n", name);}if (remove_child(current_dir, target)) {free_node(target); // 遞歸釋放子節點和自身內存printf("rm: '%s' 刪除成功。\n", name);} else {printf("rm: 刪除 '%s' 失敗。\n", name);}
}// --- 命令實現:touch ---
void cmd_touch(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target != NULL) {target->modification_time = time(NULL);printf("touch: 文件 '%s' 時間戳已更新。\n", name);return;}FileSystemNode* new_file = create_node(name, NODE_TYPE_FILE, current_dir);if (add_child(current_dir, new_file)) {printf("touch: 文件 '%s' 創建成功。\n", name);} else {free_node(new_file);}
}// --- 命令實現:cat (簡化版,只讀文件內容) ---
void cmd_cat(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("cat: '%s' 不存在。\n", name);return;}if (target->type != NODE_TYPE_FILE) {printf("cat: '%s' 不是文件。\n", name);return;}printf("--- 文件 '%s' 內容 ---\n", name);printf("%s\n", target->data.file.content);printf("------------------------\n");
}// --- 命令實現:echo (簡化版,寫入文件內容) ---
void cmd_echo(const char* content, const char* filename, bool append) {FileSystemNode* target = find_child(current_dir, filename);if (target == NULL) {// 文件不存在,則創建target = create_node(filename, NODE_TYPE_FILE, current_dir);if (!add_child(current_dir, target)) {free_node(target);printf("echo: 無法創建文件 '%s'。\n", filename);return;}printf("echo: 文件 '%s' 創建成功。\n", filename);} else if (target->type != NODE_TYPE_FILE) {printf("echo: '%s' 不是文件。\n", filename);return;}if (append) {// 追加內容int current_len = target->data.file.content_len;int content_to_add_len = strlen(content);if (current_len + content_to_add_len + 1 > MAX_FILE_CONTENT_LEN) {printf("echo: 文件 '%s' 內容超出最大限制。\n", filename);return;}strcat(target->data.file.content, content);target->data.file.content_len += content_to_add_len;printf("echo: 內容已追加到文件 '%s'。\n", filename);} else {// 覆蓋內容strncpy(target->data.file.content, content, MAX_FILE_CONTENT_LEN - 1);target->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';target->data.file.content_len = strlen(content);printf("echo: 內容已寫入文件 '%s'。\n", filename);}target->modification_time = time(NULL);
}// --- 命令實現:cp (簡化版,只支持文件到文件,或文件到目錄) ---
void cmd_cp(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("cp: 源文件或目錄 '%s' 不存在。\n", source_name);return;}if (source->type == NODE_TYPE_DIR) {printf("cp: 暫不支持目錄復制,請使用 -r 選項 (本模擬未實現)。\n");return;}FileSystemNode* dest = find_child(current_dir, dest_name);if (dest != NULL && dest->type == NODE_TYPE_DIR) {// 目標是目錄,復制到目錄下,名稱不變FileSystemNode* new_file = create_node(source->name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(dest, new_file)) {printf("cp: 文件 '%s' 已復制到目錄 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 到目錄 '%s' 失敗。\n", source_name, dest_name);}} else if (dest != NULL && dest->type == NODE_TYPE_FILE) {// 目標是文件,覆蓋strncpy(dest->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);dest->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';dest->data.file.content_len = source->data.file.content_len;dest->modification_time = time(NULL);printf("cp: 文件 '%s' 已覆蓋文件 '%s'。\n", source_name, dest_name);} else {// 目標不存在,創建新文件FileSystemNode* new_file = create_node(dest_name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(current_dir, new_file)) {printf("cp: 文件 '%s' 已復制為 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 失敗。\n", source_name);}}
}// --- 命令實現:mv (簡化版,只支持文件到文件,或文件到目錄) ---
void cmd_mv(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("mv: 源文件或目錄 '%s' 不存在。\n", source_name);return;}FileSystemNode* dest_parent = current_dir; // 默認目標在當前目錄char actual_dest_name[MAX_NAME_LEN];strncpy(actual_dest_name, dest_name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';FileSystemNode* dest_node = find_child(current_dir, dest_name);if (dest_node != NULL && dest_node->type == NODE_TYPE_DIR) {// 目標是目錄,移動到目錄下,名稱不變dest_parent = dest_node;strncpy(actual_dest_name, source->name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';if (find_child(dest_parent, actual_dest_name) != NULL) {printf("mv: 目標目錄 '%s' 中已存在同名文件或目錄 '%s'。\n", dest_name, actual_dest_name);return;}} else if (dest_node != NULL && dest_node->type == NODE_TYPE_FILE) {// 目標是文件,覆蓋并重命名if (remove_child(current_dir, dest_node)) {free_node(dest_node);printf("mv: 目標文件 '%s' 已被覆蓋。\n", dest_name);} else {printf("mv: 無法覆蓋目標文件 '%s'。\n", dest_name);return;}} else {// 目標不存在,視為重命名或移動到不存在的目錄(本模擬簡化為重命名)// 如果目標路徑是多級目錄,本模擬不支持}// 移動操作:從原父目錄移除,添加到新父目錄if (remove_child(current_dir, source)) {strncpy(source->name, actual_dest_name, MAX_NAME_LEN - 1);source->name[MAX_NAME_LEN - 1] = '\0';if (add_child(dest_parent, source)) {printf("mv: '%s' 已移動/重命名為 '%s'。\n", source_name, dest_name);} else {// 如果添加到新目錄失敗,嘗試放回原目錄 (復雜回滾,這里簡化不實現)fprintf(stderr, "mv: 移動/重命名失敗,數據可能丟失。\n");}} else {printf("mv: 無法從當前目錄移除 '%s'。\n", source_name);}
}// --- 命令解析器 ---
void parse_and_execute_command(char* command_line) {char* token;char* args[MAX_CHILDREN + 1]; // 命令 + 參數int arg_count = 0;// 復制命令行,因為strtok會修改原字符串char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n"); // 按空格、制表符、換行符分割while (token != NULL && arg_count < MAX_CHILDREN + 1) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}args[arg_count] = NULL; // 標記參數結束if (arg_count == 0) {free(cmd_copy);return; // 空命令}const char* cmd = args[0];if (strcmp(cmd, "ls") == 0) {bool show_all = false;bool long_format = false;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-a") == 0) show_all = true;if (strcmp(args[i], "-l") == 0) long_format = true;if (strcmp(args[i], "-lh") == 0) { long_format = true; /* 簡化:lh和l一樣 */ }}cmd_ls(current_dir, show_all, long_format);} else if (strcmp(cmd, "pwd") == 0) {cmd_pwd(current_dir);} else if (strcmp(cmd, "cd") == 0) {if (arg_count > 1) {cmd_cd(args[1]);} else {cmd_cd("~"); // 模擬cd默認回主目錄}} else if (strcmp(cmd, "mkdir") == 0) {if (arg_count > 1) {cmd_mkdir(args[1]);} else {printf("mkdir: 缺少操作數。\n");}} else if (strcmp(cmd, "rm") == 0) {if (arg_count > 1) {bool recursive = false;char* target_name = NULL;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-r") == 0 || strcmp(args[i], "-rf") == 0) {recursive = true;} else {target_name = args[i];}}if (target_name) {cmd_rm(target_name, recursive);} else {printf("rm: 缺少操作數。\n");}} else {printf("rm: 缺少操作數。\n");}} else if (strcmp(cmd, "touch") == 0) {if (arg_count > 1) {cmd_touch(args[1]);} else {printf("touch: 缺少操作數。\n");}} else if (strcmp(cmd, "cat") == 0) {if (arg_count > 1) {cmd_cat(args[1]);} else {printf("cat: 缺少操作數。\n");}} else if (strcmp(cmd, "echo") == 0) {if (arg_count > 2) {bool append = false;char* content = args[1];char* filename = NULL;// 簡化處理:查找 ">" 或 ">>"for (int i = 2; i < arg_count; i++) {if (strcmp(args[i], ">") == 0) {if (i + 1 < arg_count) filename = args[i+1];break;} else if (strcmp(args[i], ">>") == 0) {append = true;if (i + 1 < arg_count) filename = args[i+1];break;}}if (filename) {cmd_echo(content, filename, append);} else {printf("echo: 缺少重定向目標文件。\n");}} else {printf("echo: 缺少操作數。\n");}} else if (strcmp(cmd, "cp") == 0) {if (arg_count > 2) {cmd_cp(args[1], args[2]);} else {printf("cp: 缺少操作數。\n");}} else if (strcmp(cmd, "mv") == 0) {if (arg_count > 2) {cmd_mv(args[1], args[2]);} else {printf("mv: 缺少操作數。\n");}}else {printf("sim_shell: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}// --- 主函數:模擬Shell環境 ---
int main() {printf("====== 簡易Linux Shell文件系統模擬器 ======\n");printf("輸入 'exit' 退出。\n");// 初始化根目錄root_dir = create_node("/", NODE_TYPE_DIR, NULL);current_dir = root_dir;// 預設一些文件和目錄FileSystemNode* home = create_node("home", NODE_TYPE_DIR, root_dir);add_child(root_dir, home);FileSystemNode* user = create_node("user", NODE_TYPE_DIR, home);add_child(home, user);FileSystemNode* dev = create_node("dev", NODE_TYPE_DIR, root_dir);add_child(root_dir, dev);FileSystemNode* proc = create_node("proc", NODE_TYPE_DIR, root_dir);add_child(root_dir, proc);FileSystemNode* etc = create_node("etc", NODE_TYPE_DIR, root_dir);add_child(root_dir, etc);// 模擬一些文件FileSystemNode* readme = create_node("README.txt", NODE_TYPE_FILE, user);add_child(user, readme);cmd_echo("Welcome to the simulated Linux file system!", "README.txt", false); // 寫入內容FileSystemNode* profile = create_node(".profile", NODE_TYPE_FILE, user); // 隱藏文件add_child(user, profile);cmd_echo("This is a hidden profile file.", ".profile", false);char command_line[MAX_PATH_LEN];while (true) {printf("\nsim_shell@sim_linux:%s$ ", current_dir->name);if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break; // 讀取失敗或EOF}// 移除換行符command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_command(command_line);}printf("\n====== 模擬器退出,釋放內存 ======\n");free_node(root_dir); // 釋放所有文件系統節點內存return 0;
}
代碼分析與邏輯透析:
這份C語言代碼模擬了一個簡易的Linux Shell文件系統,讓你能從底層理解文件和目錄操作的原理。雖然它不是真實的操作系統,但其核心數據結構和邏輯與真實文件系統有異曲同工之妙。
FileSystemNode
結構體:這是文件系統的基本單元,可以表示文件或目錄。
name
:存儲文件或目錄的名稱。type
:通過枚舉NodeType
區分是目錄還是文件。creation_time
,modification_time
:模擬文件的時間戳,ls -l
命令會用到。parent
:指向父目錄的指針,這是構建文件系統樹的關鍵,用于cd ..
和pwd
命令。union data
:這是一個聯合體(Union),它允許在同一塊內存區域存儲不同類型的數據。如果是目錄 (
NODE_TYPE_DIR
),data.dir
會存儲一個children
數組(子節點指針)和num_children
(子節點數量)。如果是文件 (
NODE_TYPE_FILE
),data.file
會存儲content
數組(文件內容)和content_len
。為什么用聯合體? 因為一個節點要么是目錄,要么是文件,它們的數據是互斥的,使用聯合體可以節省內存。
root_dir
和current_dir
全局變量:root_dir
:指向文件系統樹的根目錄,是整個文件系統的起點。current_dir
:指向用戶當前所在的工作目錄,所有相對路徑的操作都以此為基準。
輔助函數 (
create_node
,find_child
,add_child
,remove_child
,free_node
):這些函數是文件系統操作的“基本功”。
create_node
:動態分配內存并初始化一個新的文件或目錄節點。find_child
:在給定目錄下查找指定名稱的子節點。add_child
:將一個新節點添加到父目錄的子節點列表中。remove_child
:從父目錄中移除一個子節點。free_node
:遞歸釋放文件系統樹中某個節點及其所有子節點的內存。這是防止內存泄漏的關鍵!
命令實現函數 (
cmd_ls
,cmd_pwd
,cmd_cd
,cmd_mkdir
,cmd_rm
,cmd_touch
,cmd_cat
,cmd_echo
,cmd_cp
,cmd_mv
):每個函數都對應一個Linux Shell命令,實現了其核心邏輯。
cmd_ls
: 遍歷當前目錄的子節點,根據show_all
(-a參數)和long_format
(-l參數)打印不同的信息。cmd_pwd
: 從當前目錄開始,通過parent
指針向上回溯,逐級拼接目錄名,直到根目錄,從而構建出完整的絕對路徑。cmd_cd
: 根據目標路徑(/
,..
,.
, 子目錄名),更新current_dir
指針。cmd_mkdir
: 創建一個新目錄節點,并將其添加到當前目錄的子節點列表中。cmd_rm
: 刪除文件或目錄。注意rm -r
的遞歸刪除邏輯(本模擬中free_node
會遞歸釋放)。cmd_touch
: 如果文件存在,更新其修改時間;如果不存在,則創建一個空文件。cmd_cat
: 打印文件的內容。cmd_echo
: 模擬echo > file
(覆蓋)和echo >> file
(追加)功能,修改文件內容。cmd_cp
: 模擬文件復制,包括復制到目錄和文件重命名。cmd_mv
: 模擬文件移動或重命名。
parse_and_execute_command
函數:這是Shell的核心!它負責解析用戶輸入的命令行字符串。
strtok
函數:用于將命令行按空格、制表符、換行符等分隔符拆分成一個個的“令牌”(token),即命令名和參數。根據解析出的命令名,調用對應的
cmd_
函數來執行。
main
函數:初始化文件系統:創建根目錄和一些預設的子目錄(
home
,dev
,proc
,etc
)和文件(README.txt
,.profile
)。進入一個無限循環,模擬Shell的交互式命令行界面:
打印當前路徑提示符 (
sim_shell@sim_linux:%s$
)。使用
fgets
讀取用戶輸入的命令行。處理
exit
命令退出循環。調用
parse_and_execute_command
解析并執行命令。
最后,在程序退出前,調用
free_node(root_dir)
釋放所有動態分配的內存,避免內存泄漏。
通過這個模擬器,你不僅能練習C語言的結構體、聯合體、指針、動態內存分配、字符串操作等高級特性,還能對Linux文件系統的底層邏輯有一個更直觀、更深入的理解!
1.4 軟件包管理:Linux的“應用商店”
在Windows上,你可能習慣了從官網下載安裝包,或者用各種“管家”來安裝軟件。但在Linux上,我們有更高效、更統一的方式——軟件包管理系統。
1.4.1 為什么需要軟件包管理?
想象一下,如果你想在Windows上安裝一個軟件,你需要:
找到官網。
下載安裝包。
雙擊運行,一步步安裝。
如果這個軟件依賴其他庫,你可能還要手動安裝這些庫。
更新時,又要重復上述步驟。
而在Linux上,軟件包管理系統解決了這些痛點:
統一管理: 所有軟件都以“軟件包”的形式存在,由軟件包管理器統一安裝、更新、卸載。
依賴解決: 當你安裝一個軟件時,軟件包管理器會自動檢測并安裝它所依賴的其他軟件庫,無需你手動尋找。
版本控制: 能夠方便地管理軟件版本,回滾到舊版本,或升級到新版本。
安全可靠: 軟件包通常來自官方或信任的軟件源,經過驗證,減少了安全風險。
方便快捷: 只需要一條簡單的命令,就能完成軟件的安裝和管理。
1.4.2 主流軟件包管理器:APT與YUM/DNF
不同的Linux發行版使用不同的軟件包管理器。
APT (Advanced Package Tool):
代表發行版: Debian、Ubuntu、Linux Mint等(基于Debian系的發行版)。
特點: 強大、靈活、易用。
核心概念:
軟件包(Package): 通常是
.deb
格式的文件,包含了軟件的可執行文件、庫、配置文件、文檔等。軟件源(Repository): 存儲軟件包的服務器地址。
apt
通過訪問這些源來獲取軟件包信息和下載軟件包。apt-get
/apt
:apt-get
是老牌的命令行工具,apt
是新一代的命令行工具,更用戶友好,推薦使用apt
。
YUM (Yellowdog Updater Modified) / DNF (Dandified YUM):
代表發行版: Red Hat Enterprise Linux (RHEL)、CentOS、Fedora等(基于Red Hat系的發行版)。
特點:
yum
是老牌的工具,dnf
是yum
的下一代版本,性能更好,推薦使用dnf
。核心概念:
軟件包: 通常是
.rpm
格式的文件。軟件源: 存儲軟件包的服務器地址。
表格:APT與YUM/DNF常用命令對比
功能 | APT (Debian/Ubuntu) 命令 | YUM/DNF (Red Hat/CentOS/Fedora) 命令 | 備注 |
---|---|---|---|
更新本地軟件包索引 |
|
| 必須先更新索引,才能獲取最新的軟件包信息。 |
安裝軟件包 |
|
| 自動解決依賴。 |
卸載軟件包 |
|
| 卸載軟件,保留配置文件。 |
完全卸載軟件包 |
|
| 徹底刪除軟件及其配置文件。 |
升級所有可升級的軟件包 |
|
| 升級已安裝的軟件到最新版本。 |
搜索軟件包 |
|
| 查找包含特定關鍵字的軟件包。 |
列出已安裝軟件包 |
|
| 查看系統中已安裝的軟件包。 |
顯示軟件包信息 |
|
| 查看軟件包的詳細信息(版本、大小、描述等)。 |
清理不再需要的軟件包 |
|
| 清理不再需要的依賴包或緩存。 |
1.4.3 軟件源配置:你的“倉庫清單”
軟件包管理器通過讀取軟件源配置文件來知道去哪里下載軟件包。
APT: 配置文件通常在
/etc/apt/sources.list
和/etc/apt/sources.list.d/
目錄下。你可以編輯這些文件來添加、刪除或修改軟件源。通常會選擇國內的鏡像源(如阿里云、清華大學),以提高下載速度。
格式示例:
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb
:表示二進制軟件包。http://mirrors.aliyun.com/ubuntu/
:軟件源的URL地址。jammy
:Ubuntu的版本代號。main restricted universe multiverse
:軟件包的分類。
YUM/DNF: 配置文件通常在
/etc/yum.repos.d/
目錄下,以.repo
結尾。
C語言模擬:一個簡易的軟件包管理器
我們來用C語言模擬一個極簡的軟件包管理器。這個模擬器將維護一個“已安裝軟件包列表”和一個“可用軟件包列表”,并實現install
和remove
命令,同時模擬簡單的依賴關系。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>// --- 宏定義:軟件包管理限制 ---
#define MAX_PACKAGE_NAME_LEN 64
#define MAX_DEPENDENCIES 5
#define MAX_PACKAGES 20// --- 結構體:軟件包定義 ---
typedef struct Package {char name[MAX_PACKAGE_NAME_LEN];char description[128];char dependencies[MAX_DEPENDENCIES][MAX_PACKAGE_NAME_LEN]; // 依賴的軟件包名稱int num_dependencies;bool is_installed; // 是否已安裝
} Package;// --- 模擬可用軟件包列表 (軟件源) ---
Package available_packages[MAX_PACKAGES];
int num_available_packages = 0;// --- 輔助函數:查找軟件包 ---
Package* find_package(const char* name) {for (int i = 0; i < num_available_packages; i++) {if (strcmp(available_packages[i].name, name) == 0) {return &available_packages[i];}}return NULL;
}// --- 輔助函數:添加可用軟件包 ---
void add_available_package(const char* name, const char* desc, const char* deps[], int num_deps) {if (num_available_packages >= MAX_PACKAGES) {fprintf(stderr, "[模擬包管理] 達到最大可用軟件包數量。\n");return;}Package* p = &available_packages[num_available_packages++];strncpy(p->name, name, MAX_PACKAGE_NAME_LEN - 1);p->name[MAX_PACKAGE_NAME_LEN - 1] = '\0';strncpy(p->description, desc, 127);p->description[127] = '\0';p->num_dependencies = 0;for (int i = 0; i < num_deps; i++) {if (p->num_dependencies < MAX_DEPENDENCIES) {strncpy(p->dependencies[p->num_dependencies], deps[i], MAX_PACKAGE_NAME_LEN - 1);p->dependencies[p->num_dependencies][MAX_PACKAGE_NAME_LEN - 1] = '\0';p->num_dependencies++;}}p->is_installed = false;printf("[模擬包管理] 添加可用軟件包: %s\n", name);
}// --- 軟件包安裝邏輯 ---
void install_package_recursive(const char* package_name) {Package* p = find_package(package_name);if (p == NULL) {printf("錯誤: 軟件包 '%s' 不存在。\n", package_name);return;}if (p->is_installed) {printf("軟件包 '%s' 已安裝。\n", package_name);return;}printf("正在安裝軟件包 '%s'...\n", package_name);// 遞歸安裝依賴for (int i = 0; i < p->num_dependencies; i++) {Package* dep_p = find_package(p->dependencies[i]);if (dep_p != NULL && !dep_p->is_installed) {printf(" 正在安裝依賴 '%s'...\n", dep_p->name);install_package_recursive(dep_p->name); // 遞歸調用}}// 模擬安裝過程p->is_installed = true;printf("軟件包 '%s' 安裝成功!\n", package_name);
}// --- 軟件包卸載邏輯 ---
void remove_package_recursive(const char* package_name) {Package* p = find_package(package_name);if (p == NULL) {printf("錯誤: 軟件包 '%s' 不存在。\n", package_name);return;}if (!p->is_installed) {printf("軟件包 '%s' 未安裝。\n", package_name);return;}// 檢查是否有其他已安裝軟件包依賴于此軟件包for (int i = 0; i < num_available_packages; i++) {Package* other_p = &available_packages[i];if (other_p->is_installed) {for (int j = 0; j < other_p->num_dependencies; j++) {if (strcmp(other_p->dependencies[j], package_name) == 0) {printf("錯誤: 軟件包 '%s' 被已安裝的 '%s' 依賴,無法卸載。\n", package_name, other_p->name);return;}}}}// 模擬卸載過程p->is_installed = false;printf("軟件包 '%s' 卸載成功!\n", package_name);
}// --- 命令實現:list ---
void cmd_list_installed_packages() {printf("\n--- 已安裝軟件包列表 ---\n");bool found = false;for (int i = 0; i < num_available_packages; i++) {if (available_packages[i].is_installed) {printf("- %s: %s\n", available_packages[i].name, available_packages[i].description);found = true;}}if (!found) {printf("(無已安裝軟件包)\n");}printf("------------------------\n");
}// --- 命令解析器 (簡化版,只處理 install, remove, list) ---
void parse_and_execute_package_command(char* command_line) {char* token;char* args[3]; // 命令 + 軟件包名int arg_count = 0;char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n");while (token != NULL && arg_count < 3) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}if (arg_count == 0) {free(cmd_copy);return;}const char* cmd = args[0];if (strcmp(cmd, "install") == 0) {if (arg_count > 1) {install_package_recursive(args[1]);} else {printf("install: 缺少軟件包名稱。\n");}} else if (strcmp(cmd, "remove") == 0) {if (arg_count > 1) {remove_package_recursive(args[1]);} else {printf("remove: 缺少軟件包名稱。\n");}} else if (strcmp(cmd, "list") == 0) {cmd_list_installed_packages();} else {printf("sim_pkg_mgr: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}int main() {printf("====== 簡易Linux軟件包管理器模擬器 ======\n");printf("可用命令: install <pkg_name>, remove <pkg_name>, list, exit\n");// 預設一些可用軟件包const char* deps_libcurl[] = {"libssl", "libz"};add_available_package("curl", "一個命令行工具,用于傳輸數據。", deps_libcurl, 2);const char* deps_nginx[] = {"pcre", "zlib", "openssl"};add_available_package("nginx", "高性能HTTP和反向代理服務器。", deps_nginx, 3);const char* deps_git[] = {"zlib"};add_available_package("git", "分布式版本控制系統。", deps_git, 1);const char* deps_libssl[] = {};add_available_package("libssl", "SSL/TLS加密庫。", deps_libssl, 0);const char* deps_libz[] = {};add_available_package("libz", "數據壓縮庫。", deps_libz, 0);const char* deps_pcre[] = {};add_available_package("pcre", "Perl兼容正則表達式庫。", deps_pcre, 0);const char* deps_openssl[] = {"libssl", "libz"}; // openssl也依賴libssl和libzadd_available_package("openssl", "SSL/TLS工具包。", deps_openssl, 2);char command_line[MAX_PACKAGE_NAME_LEN * 2];while (true) {printf("\nsim_pkg_mgr$ ");if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break;}command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_package_command(command_line);}printf("\n====== 模擬器退出 ======\n");return 0;
}
代碼分析與邏輯透析:
這段C語言代碼模擬了一個簡易的Linux軟件包管理系統,讓你能從底層理解軟件包安裝、卸載和依賴解決的原理。
Package
結構體:代表一個軟件包,包含名稱、描述、依賴列表和安裝狀態。
dependencies
:一個二維字符數組,存儲該軟件包所依賴的其他軟件包的名稱。is_installed
:布爾類型,標記該軟件包是否已安裝在系統中。
available_packages
全局數組:模擬了軟件源中所有可用的軟件包。在真實系統中,這對應于
apt update
或yum check-update
后從軟件源獲取的軟件包列表。
輔助函數 (
find_package
,add_available_package
):find_package
:根據名稱在available_packages
列表中查找軟件包。add_available_package
:用于向模擬的軟件源中添加新的軟件包定義。
install_package_recursive
函數:這是軟件包安裝的核心邏輯,它實現了遞歸安裝依賴的功能。
當用戶請求安裝一個軟件包時,它首先檢查該軟件包是否已安裝。
如果未安裝,它會遍歷該軟件包的所有依賴項。對于每個未安裝的依賴項,它會遞歸調用自身來安裝該依賴項。
所有依賴安裝完成后,才會“安裝”當前軟件包(即將
is_installed
標志設置為true
)。這種遞歸方式確保了所有依賴項在主軟件包安裝之前都被滿足。
remove_package_recursive
函數:這是軟件包卸載的核心邏輯。
在卸載一個軟件包之前,它會遍歷所有已安裝的軟件包,檢查是否有任何其他已安裝的軟件包依賴于當前要卸載的軟件包。
如果存在依賴關系,則會阻止卸載,并給出提示。這模擬了真實軟件包管理器在卸載關鍵庫時會阻止你的行為,防止系統崩潰。
如果沒有被其他已安裝軟件包依賴,則將
is_installed
標志設置為false
,模擬卸載成功。
cmd_list_installed_packages
函數:遍歷
available_packages
列表,打印所有is_installed
為true
的軟件包。
parse_and_execute_package_command
函數:解析用戶輸入的命令(
install
,remove
,list
)和軟件包名稱。調用對應的安裝或卸載函數。
main
函數:通過
add_available_package
預設了一些模擬的軟件包及其依賴關系,例如curl
依賴libssl
和libz
,nginx
依賴pcre
、zlib
和openssl
。進入一個無限循環,模擬軟件包管理器的命令行界面,等待用戶輸入命令。
通過這個模擬器,你不僅能練習C語言的結構體、字符串操作、遞歸等高級特性,還能對Linux軟件包管理系統(特別是依賴解決)的底層邏輯有一個更直觀、更深入的理解!
1.5 小結與展望
恭喜你,老鐵!你已經成功邁出了“Linux與C高級編程”學習之路的第一步:初識Linux與環境搭建,玩轉軟件包管理!
在這一部分中,我們:
深入理解了Linux在嵌入式領域的“無冕之王”地位,以及它在性能、成本、靈活性等方面的巨大優勢。
了解了常見的Linux發行版,并為你推薦了適合學習和開發的“座駕”。
手把手教你搭建Linux開發環境,無論是穩妥的虛擬機(VMware/VirtualBox)還是輕量級的WSL,甚至云服務器,你都有了選擇。
掌握了Linux Shell最常用的“武功秘籍”——文件和目錄操作命令,讓你在文件系統中“穿梭自如”。
更重要的是,我們通過一個龐大的C語言模擬器,讓你從底層理解了Linux文件系統命令背后的數據結構和邏輯。
最后,我們揭秘了Linux的“應用商店”——軟件包管理系統,理解了它在統一管理、依賴解決、安全可靠等方面的巨大作用,并對比了APT和YUM/DNF的常用命令。
我們還用一個簡易的C語言軟件包管理器模擬器,讓你親身體驗了軟件包安裝、卸載和依賴解決的遞歸邏輯。
這些知識,是你在Linux環境下進行嵌入式開發的基礎。沒有它們,你就像一個沒有地圖的探險家,寸步難行。
接下來,我們將進入更激動人心的第二部分:Linux Shell命令(權限、輸入輸出,文件搜索、文件處理,壓縮解壓,進程管理、用戶管理)!我們將深入Shell命令的更多高級用法,讓你真正成為一個Linux命令行高手!
請記住,學習Linux,多敲命令,多實踐是王道!不要害怕命令行,它會成為你最忠實、最強大的伙伴!
敬請期待我的下一次更新!如果你在學習過程中有任何疑問,或者對代碼有任何改進的想法,隨時在評論區告訴我,咱們一起交流,一起成為Linux與C編程的“大神”!
--------------------------------------------------------------------------------------------------------------------------------更新于2025.6.13 下午5:40
【硬核揭秘】Linux與C高級編程:從入門到精通,你的全棧之路!
第二部分:Linux Shell命令進階——掌控權限、玩轉I/O、搜索處理、進程用戶全解析
嘿,各位C語言的“卷王”們!
上一篇,咱們初識了Linux,搭建了環境,還學會了文件和目錄的基本操作,以及軟件包管理。是不是感覺Linux命令行也沒那么神秘了?
今天,咱們要繼續深入Linux的“腹地”,解鎖更多強大的Shell命令!這些命令,就像你的“武功秘籍”中的高級招式,能讓你在Linux世界中更加游刃有余,無論是處理文件、管理進程,還是進行系統維護,都能事半功倍!
本篇是“Linux與C高級編程”系列的第二部分,我們將帶你:
文件權限: 深入理解Linux文件權限的奧秘,學會
chmod
、chown
、chgrp
等命令,讓你的文件“聽話”。輸入輸出重定向與管道: 掌握Shell的“乾坤大挪移”和“內力傳輸”之術,讓命令之間高效協作。
文件搜索與處理: 學習
find
、grep
、diff
等命令,成為文件世界的“火眼金睛”和“庖丁解牛”。壓縮與解壓: 掌握
tar
、gzip
等命令,讓你的文件“縮骨增力”。進程管理: 學習
ps
、top
、kill
等命令,成為系統進程的“統帥”。用戶與組管理: 了解
useradd
、groupadd
等命令,掌握用戶和組的“治理之道”。
每個知識點,咱們都會結合C語言模擬,讓你不僅知道怎么用,更理解其背后的原理!
準備好了嗎?咱們這就開始,讓你的Linux技能,再上一個臺階!
2.1 文件權限:Linux安全的“基石”
在Linux中,一切皆文件。而文件權限,則是Linux系統安全的“基石”。它決定了誰能對文件或目錄進行什么操作。
2.1.1 用戶、組與“其他人”:誰能動我的文件?
Linux將用戶分為三類,并對文件或目錄賦予相應的權限:
所有者(Owner): 文件或目錄的創建者,通常是文件的擁有者。
組(Group): 文件或目錄所屬的組。組內的所有用戶都擁有對該文件或目錄的組權限。
其他人(Others): 除所有者和組內用戶之外的所有系統用戶。
2.1.2 讀、寫、執行權限:能做什么?
每種用戶類型(所有者、組、其他人)都擁有三種基本權限:
讀(Read,
r
):對文件: 可以查看文件內容。
對目錄: 可以列出目錄下的文件和子目錄名稱(但不能進入)。
寫(Write,
w
):對文件: 可以修改或刪除文件內容。
對目錄: 可以在目錄下創建、刪除、重命名文件或子目錄。
執行(Execute,
x
):對文件: 可以運行可執行文件(腳本或二進制程序)。
對目錄: 可以進入目錄,并訪問目錄下的文件和子目錄。
表格:權限表示與含義
符號表示 | 數字表示(八進制) | 含義 |
---|---|---|
|
| 讀權限 |
|
| 寫權限 |
|
| 執行權限 |
|
| 無任何權限 |
這些權限可以組合使用,例如:
rwx
= 4 + 2 + 1 = 7 (讀、寫、執行)rw-
= 4 + 2 + 0 = 6 (讀、寫)r-x
= 4 + 0 + 1 = 5 (讀、執行)
2.1.3 ls -l
:文件權限的“照妖鏡”
使用ls -l
命令,可以查看文件或目錄的詳細信息,包括權限。
$ ls -l my_file.txt my_dir/
-rw-r--r-- 1 user group 1024 May 10 10:00 my_file.txt
drwxr-xr-x 2 user group 4096 May 10 10:05 my_dir/
解析:
第一列:文件類型和權限
第一個字符:
-
:普通文件d
:目錄l
:鏈接文件
后面9個字符:分為三組,每組3個字符,分別代表所有者、組、其他人的權限。
rw-
:所有者有讀寫權限,無執行權限。r--
:組有讀權限,無寫和執行權限。r--
:其他人有讀權限,無寫和執行權限。
第二列:硬鏈接數
第三列:所有者
第四列:所屬組
第五列:文件大小(字節)
第六列:最后修改時間
第七列:文件或目錄名稱
2.1.4 chmod
:修改文件權限的“金手指”
chmod
(change mode) 命令用于修改文件或目錄的權限。
符號模式(Symbolic Mode):
u
(user): 所有者g
(group): 組o
(others): 其他人a
(all): 所有用戶 (u+g+o)+
:添加權限-
:移除權限=
:設置權限
chmod u+x file.sh # 給所有者添加執行權限 chmod g-w file.txt # 移除組的寫權限 chmod o=r file.txt # 設置其他人的權限為只讀 chmod a+rw my_dir/ # 給所有用戶添加讀寫權限 chmod -R u+x my_scripts/ # 遞歸地給my_scripts目錄及其子目錄下的所有文件添加所有者的執行權限
數字模式(Octal Mode):
使用八進制數字表示權限,每位數字代表
rwx
的組合值。格式:
chmod <所有者權限><組權限><其他人權限> <file/dir>
例如:
rwx
= 7,rw-
= 6,r-x
= 5,r--
= 4,--x
= 1,---
= 0
chmod 755 file.sh # 所有者rwx,組r-x,其他人r-x chmod 644 file.txt # 所有者rw-,組r--,其他人r-- chmod 777 my_dir/ # 所有用戶rwx (不推薦,不安全)
2.1.5 chown
與 chgrp
:修改文件所有者和所屬組
chown
(change owner): 修改文件或目錄的所有者。sudo chown new_owner file.txt # 將file.txt的所有者改為new_owner sudo chown new_owner:new_group file.txt # 同時修改所有者和所屬組 sudo chown -R new_owner my_dir/ # 遞歸修改目錄所有者
通常需要
sudo
權限才能執行。
chgrp
(change group): 修改文件或目錄的所屬組。sudo chgrp new_group file.txt # 將file.txt的所屬組改為new_group sudo chgrp -R new_group my_dir/ # 遞歸修改目錄所屬組
C語言模擬:文件權限管理(chmod
八進制模式)
我們將擴展之前的文件系統模擬器,在FileSystemNode
中添加權限字段,并實現一個簡化的chmod
命令(只支持八進制模式)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h> // 用于模擬時間戳// --- 宏定義:文件系統限制 ---
#define MAX_NAME_LEN 64 // 文件/目錄名最大長度
#define MAX_CHILDREN 10 // 每個目錄最大子節點數
#define MAX_PATH_LEN 256 // 路徑最大長度
#define MAX_FILE_CONTENT_LEN 1024 // 文件內容最大長度// --- 枚舉:節點類型 ---
typedef enum {NODE_TYPE_DIR, // 目錄NODE_TYPE_FILE // 文件
} NodeType;// --- 結構體:文件/目錄節點 ---
typedef struct FileSystemNode {char name[MAX_NAME_LEN]; // 節點名稱NodeType type; // 節點類型 (目錄或文件)time_t creation_time; // 創建時間time_t modification_time; // 修改時間uint16_t permissions; // 權限 (例如:0755,八進制表示)char owner[MAX_NAME_LEN]; // 所有者 (模擬)char group[MAX_NAME_LEN]; // 所屬組 (模擬)struct FileSystemNode* parent; // 父節點指針union {// 如果是目錄,存儲子節點struct {struct FileSystemNode* children[MAX_CHILDREN];int num_children;} dir;// 如果是文件,存儲文件內容struct {char content[MAX_FILE_CONTENT_LEN];int content_len;} file;} data;
} FileSystemNode;// --- 全局變量:模擬文件系統根目錄和當前工作目錄 ---
FileSystemNode* root_dir = NULL;
FileSystemNode* current_dir = NULL;
char current_user[MAX_NAME_LEN] = "sim_user"; // 模擬當前用戶
char current_group[MAX_NAME_LEN] = "sim_group"; // 模擬當前組// --- 輔助函數:創建新節點 ---
FileSystemNode* create_node(const char* name, NodeType type, FileSystemNode* parent) {FileSystemNode* new_node = (FileSystemNode*)malloc(sizeof(FileSystemNode));if (new_node == NULL) {fprintf(stderr, "內存分配失敗!\n");exit(EXIT_FAILURE);}strncpy(new_node->name, name, MAX_NAME_LEN - 1);new_node->name[MAX_NAME_LEN - 1] = '\0';new_node->type = type;new_node->creation_time = time(NULL);new_node->modification_time = time(NULL);new_node->permissions = (type == NODE_TYPE_DIR) ? 0755 : 0644; // 默認權限strncpy(new_node->owner, current_user, MAX_NAME_LEN - 1);new_node->owner[MAX_NAME_LEN - 1] = '\0';strncpy(new_node->group, current_group, MAX_NAME_LEN - 1);new_node->group[MAX_NAME_LEN - 1] = '\0';new_node->parent = parent;if (type == NODE_TYPE_DIR) {new_node->data.dir.num_children = 0;for (int i = 0; i < MAX_CHILDREN; i++) {new_node->data.dir.children[i] = NULL;}} else { // NODE_TYPE_FILEnew_node->data.file.content[0] = '\0';new_node->data.file.content_len = 0;}return new_node;
}// --- 輔助函數:查找子節點 ---
FileSystemNode* find_child(FileSystemNode* parent, const char* name) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return NULL;for (int i = 0; i < parent->data.dir.num_children; i++) {if (strcmp(parent->data.dir.children[i]->name, name) == 0) {return parent->data.dir.children[i];}}return NULL;
}// --- 輔助函數:添加子節點 ---
bool add_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return false;if (parent->data.dir.num_children >= MAX_CHILDREN) {fprintf(stderr, "目錄 %s 子節點已滿。\n", parent->name);return false;}parent->data.dir.children[parent->data.dir.num_children++] = child;child->parent = parent; // 確保子節點的父指針正確parent->modification_time = time(NULL); // 更新父目錄修改時間return true;
}// --- 輔助函數:移除子節點 ---
bool remove_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR || child == NULL) return false;for (int i = 0; i < parent->data.dir.num_children; i++) {if (parent->data.dir.children[i] == child) {// 移動最后一個子節點到當前位置,然后減少子節點計數parent->data.dir.children[i] = parent->data.dir.children[parent->data.dir.num_children - 1];parent->data.dir.children[parent->data.dir.num_children - 1] = NULL;parent->data.dir.num_children--;parent->modification_time = time(NULL);return true;}}return false;
}// --- 輔助函數:遞歸釋放文件系統節點內存 ---
void free_node(FileSystemNode* node) {if (node == NULL) return;if (node->type == NODE_TYPE_DIR) {for (int i = 0; i < node->data.dir.num_children; i++) {free_node(node->data.dir.children[i]);}}free(node);
}// --- 輔助函數:將八進制權限轉換為rwx字符串 ---
void get_permission_string(uint16_t perm, char* perm_str) {perm_str[0] = (perm & 0400) ? 'r' : '-'; // 所有者讀perm_str[1] = (perm & 0200) ? 'w' : '-'; // 所有者寫perm_str[2] = (perm & 0100) ? 'x' : '-'; // 所有者執行perm_str[3] = (perm & 0040) ? 'r' : '-'; // 組讀perm_str[4] = (perm & 0020) ? 'w' : '-'; // 組寫perm_str[5] = (perm & 0010) ? 'x' : '-'; // 組執行perm_str[6] = (perm & 0004) ? 'r' : '-'; // 其他人讀perm_str[7] = (perm & 0002) ? 'w' : '-'; // 其他人寫perm_str[8] = (perm & 0001) ? 'x' : '-'; // 其他人執行perm_str[9] = '\0';
}// --- 命令實現:ls ---
void cmd_ls(FileSystemNode* dir, bool show_all, bool long_format) {if (dir == NULL || dir->type != NODE_TYPE_DIR) {printf("ls: 無法訪問目錄。\n");return;}printf("目錄 '%s' 的內容:\n", dir->name);for (int i = 0; i < dir->data.dir.num_children; i++) {FileSystemNode* child = dir->data.dir.children[i];if (child == NULL) continue;if (!show_all && child->name[0] == '.') { // 不顯示隱藏文件continue;}if (long_format) {char time_str[20];strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", localtime(&child->modification_time));char perm_str[10];get_permission_string(child->permissions, perm_str);printf("%c%s %s %s %s %s %s\n",(child->type == NODE_TYPE_DIR) ? 'd' : '-',perm_str,child->owner,child->group,time_str,(child->type == NODE_TYPE_FILE) ? "FILE" : "DIR", // 簡化大小顯示child->name);} else {printf("%s\n", child->name);}}
}// --- 命令實現:pwd ---
void cmd_pwd(FileSystemNode* node) {if (node == NULL) {printf("/\n"); // 根目錄return;}char path[MAX_PATH_LEN];path[0] = '\0';FileSystemNode* temp = node;while (temp != NULL) {if (temp->parent == NULL) { // 根目錄特殊處理strncat(path, "/", MAX_PATH_LEN - strlen(path) - 1);break;}char current_segment[MAX_NAME_LEN + 1];snprintf(current_segment, sizeof(current_segment), "/%s", temp->name);strncat(current_segment, path, MAX_PATH_LEN - strlen(current_segment) - 1); // 將path拼接到current_segment后面strncpy(path, current_segment, MAX_PATH_LEN - 1); // 修正:將current_segment復制到pathpath[MAX_PATH_LEN - 1] = '\0';temp = temp->parent;}if (path[0] == '\0') { // 如果是根目錄printf("/\n");} else {printf("%s\n", path);}
}// --- 命令實現:cd ---
void cmd_cd(const char* path) {if (strcmp(path, "/") == 0) {current_dir = root_dir;printf("cd: 已切換到根目錄。\n");return;}if (strcmp(path, "..") == 0) {if (current_dir->parent != NULL) {current_dir = current_dir->parent;printf("cd: 已切換到上級目錄。\n");} else {printf("cd: 已經是根目錄。\n");}return;}if (strcmp(path, ".") == 0) {printf("cd: 仍在當前目錄。\n");return;}FileSystemNode* target_dir = find_child(current_dir, path);if (target_dir != NULL && target_dir->type == NODE_TYPE_DIR) {current_dir = target_dir;printf("cd: 已切換到目錄 '%s'。\n", path);} else {printf("cd: 目錄 '%s' 不存在或不是目錄。\n", path);}
}// --- 命令實現:mkdir ---
void cmd_mkdir(const char* name) {if (find_child(current_dir, name) != NULL) {printf("mkdir: 目錄 '%s' 已存在。\n", name);return;}FileSystemNode* new_dir = create_node(name, NODE_TYPE_DIR, current_dir);if (add_child(current_dir, new_dir)) {printf("mkdir: 目錄 '%s' 創建成功。\n", name);} else {free_node(new_dir); // 創建失敗則釋放}
}// --- 命令實現:rm ---
void cmd_rm(const char* name, bool recursive) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("rm: '%s' 不存在。\n", name);return;}if (target->type == NODE_TYPE_DIR && !recursive) {printf("rm: 無法刪除目錄 '%s',請使用 -r 選項。\n", name);return;}if (target->type == NODE_TYPE_DIR && target->data.dir.num_children > 0 && recursive) {printf("rm: 正在遞歸刪除目錄 '%s'...\n", name);}if (remove_child(current_dir, target)) {free_node(target); // 遞歸釋放子節點和自身內存printf("rm: '%s' 刪除成功。\n", name);} else {printf("rm: 刪除 '%s' 失敗。\n", name);}
}// --- 命令實現:touch ---
void cmd_touch(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target != NULL) {target->modification_time = time(NULL);printf("touch: 文件 '%s' 時間戳已更新。\n", name);return;}FileSystemNode* new_file = create_node(name, NODE_TYPE_FILE, current_dir);if (add_child(current_dir, new_file)) {printf("touch: 文件 '%s' 創建成功。\n", name);} else {free_node(new_file);}
}// --- 命令實現:cat (簡化版,只讀文件內容) ---
void cmd_cat(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("cat: '%s' 不存在。\n", name);return;}if (target->type != NODE_TYPE_FILE) {printf("cat: '%s' 不是文件。\n", name);return;}printf("--- 文件 '%s' 內容 ---\n", name);printf("%s\n", target->data.file.content);printf("------------------------\n");
}// --- 命令實現:echo (簡化版,寫入文件內容) ---
void cmd_echo(const char* content, const char* filename, bool append) {FileSystemNode* target = find_child(current_dir, filename);if (target == NULL) {// 文件不存在,則創建target = create_node(filename, NODE_TYPE_FILE, current_dir);if (!add_child(current_dir, target)) {free_node(target);printf("echo: 無法創建文件 '%s'。\n", filename);return;}printf("echo: 文件 '%s' 創建成功。\n", filename);} else if (target->type != NODE_TYPE_FILE) {printf("echo: '%s' 不是文件。\n", filename);return;}if (append) {// 追加內容int current_len = target->data.file.content_len;int content_to_add_len = strlen(content);if (current_len + content_to_add_len + 1 > MAX_FILE_CONTENT_LEN) {printf("echo: 文件 '%s' 內容超出最大限制。\n", filename);return;}strcat(target->data.file.content, content);target->data.file.content_len += content_to_add_len;printf("echo: 內容已追加到文件 '%s'。\n", filename);} else {// 覆蓋內容strncpy(target->data.file.content, content, MAX_FILE_CONTENT_LEN - 1);target->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';target->data.file.content_len = strlen(content);printf("echo: 內容已寫入文件 '%s'。\n", filename);}target->modification_time = time(NULL);
}// --- 命令實現:cp (簡化版,只支持文件到文件,或文件到目錄) ---
void cmd_cp(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("cp: 源文件或目錄 '%s' 不存在。\n", source_name);return;}if (source->type == NODE_TYPE_DIR) {printf("cp: 暫不支持目錄復制,請使用 -r 選項 (本模擬未實現)。\n");return;}FileSystemNode* dest = find_child(current_dir, dest_name);if (dest != NULL && dest->type == NODE_TYPE_DIR) {// 目標是目錄,復制到目錄下,名稱不變FileSystemNode* new_file = create_node(source->name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(dest, new_file)) {printf("cp: 文件 '%s' 已復制到目錄 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 到目錄 '%s' 失敗。\n", source_name, dest_name);}} else if (dest != NULL && dest->type == NODE_TYPE_FILE) {// 目標是文件,覆蓋strncpy(dest->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);dest->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';dest->data.file.content_len = source->data.file.content_len;dest->modification_time = time(NULL);printf("cp: 文件 '%s' 已覆蓋文件 '%s'。\n", source_name, dest_name);} else {// 目標不存在,創建新文件FileSystemNode* new_file = create_node(dest_name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(current_dir, new_file)) {printf("cp: 文件 '%s' 已復制為 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 失敗。\n", source_name);}}
}// --- 命令實現:mv (簡化版,只支持文件到文件,或文件到目錄) ---
void cmd_mv(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("mv: 源文件或目錄 '%s' 不存在。\n", source_name);return;}FileSystemNode* dest_parent = current_dir; // 默認目標在當前目錄char actual_dest_name[MAX_NAME_LEN];strncpy(actual_dest_name, dest_name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';FileSystemNode* dest_node = find_child(current_dir, dest_name);if (dest_node != NULL && dest_node->type == NODE_TYPE_DIR) {// 目標是目錄,移動到目錄下,名稱不變dest_parent = dest_node;strncpy(actual_dest_name, source->name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';if (find_child(dest_parent, actual_dest_name) != NULL) {printf("mv: 目標目錄 '%s' 中已存在同名文件或目錄 '%s'。\n", dest_name, actual_dest_name);return;}} else if (dest_node != NULL && dest_node->type == NODE_TYPE_FILE) {// 目標是文件,覆蓋并重命名if (remove_child(current_dir, dest_node)) {free_node(dest_node);printf("mv: 目標文件 '%s' 已被覆蓋。\n", dest_name);} else {printf("mv: 無法覆蓋目標文件 '%s'。\n", dest_name);return;}} else {// 目標不存在,視為重命名或移動到不存在的目錄(本模擬簡化為重命名)// 如果目標路徑是多級目錄,本模擬不支持}// 移動操作:從原父目錄移除,添加到新父目錄if (remove_child(current_dir, source)) {strncpy(source->name, actual_dest_name, MAX_NAME_LEN - 1);source->name[MAX_NAME_LEN - 1] = '\0';if (add_child(dest_parent, source)) {printf("mv: '%s' 已移動/重命名為 '%s'。\n", source_name, dest_name);} else {// 如果添加到新目錄失敗,嘗試放回原目錄 (復雜回滾,這里簡化不實現)fprintf(stderr, "mv: 移動/重命名失敗,數據可能丟失。\n");}} else {printf("mv: 無法從當前目錄移除 '%s'。\n", source_name);}
}// --- 命令實現:chmod ---
void cmd_chmod(const char* mode_str, const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("chmod: '%s' 不存在。\n", name);return;}// 將八進制字符串轉換為整數int new_permissions = 0;if (sscanf(mode_str, "%o", &new_permissions) != 1) { // %o 用于讀取八進制數printf("chmod: 無效的權限模式 '%s'。請使用三位八進制數 (例如: 755)。\n", mode_str);return;}// 檢查權限是否在有效范圍內 (000-777)if (new_permissions < 0 || new_permissions > 0777) {printf("chmod: 權限模式 '%s' 超出有效范圍 (000-777)。\n", mode_str);return;}uint16_t old_permissions = target->permissions;target->permissions = (uint16_t)new_permissions;target->modification_time = time(NULL);char old_perm_str[10], new_perm_str[10];get_permission_string(old_permissions, old_perm_str);get_permission_string(new_permissions, new_perm_str);printf("chmod: '%s' 的權限已從 %s (%03o) 更改為 %s (%03o)。\n",name, old_perm_str, old_permissions, new_perm_str, new_permissions);
}// --- 命令解析器 ---
void parse_and_execute_command(char* command_line) {char* token;char* args[MAX_CHILDREN + 1]; // 命令 + 參數int arg_count = 0;// 復制命令行,因為strtok會修改原字符串char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n"); // 按空格、制表符、換行符分割while (token != NULL && arg_count < MAX_CHILDREN + 1) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}args[arg_count] = NULL; // 標記參數結束if (arg_count == 0) {free(cmd_copy);return; // 空命令}const char* cmd = args[0];if (strcmp(cmd, "ls") == 0) {bool show_all = false;bool long_format = false;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-a") == 0) show_all = true;if (strcmp(args[i], "-l") == 0) long_format = true;if (strcmp(args[i], "-lh") == 0) { long_format = true; /* 簡化:lh和l一樣 */ }}cmd_ls(current_dir, show_all, long_format);} else if (strcmp(cmd, "pwd") == 0) {cmd_pwd(current_dir);} else if (strcmp(cmd, "cd") == 0) {if (arg_count > 1) {cmd_cd(args[1]);} else {cmd_cd("~"); // 模擬cd默認回主目錄}} else if (strcmp(cmd, "mkdir") == 0) {if (arg_count > 1) {cmd_mkdir(args[1]);} else {printf("mkdir: 缺少操作數。\n");}} else if (strcmp(cmd, "rm") == 0) {if (arg_count > 1) {bool recursive = false;char* target_name = NULL;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-r") == 0 || strcmp(args[i], "-rf") == 0) {recursive = true;} else {target_name = args[i];}}if (target_name) {cmd_rm(target_name, recursive);} else {printf("rm: 缺少操作數。\n");}} else {printf("rm: 缺少操作數。\n");}} else if (strcmp(cmd, "touch") == 0) {if (arg_count > 1) {cmd_touch(args[1]);} else {printf("touch: 缺少操作數。\n");}} else if (strcmp(cmd, "cat") == 0) {if (arg_count > 1) {cmd_cat(args[1]);} else {printf("cat: 缺少操作數。\n");}} else if (strcmp(cmd, "echo") == 0) {if (arg_count > 2) {bool append = false;char* content = args[1];char* filename = NULL;// 簡化處理:查找 ">" 或 ">>"for (int i = 2; i < arg_count; i++) {if (strcmp(args[i], ">") == 0) {if (i + 1 < arg_count) filename = args[i+1];break;} else if (strcmp(args[i], ">>") == 0) {append = true;if (i + 1 < arg_count) filename = args[i+1];break;}}if (filename) {cmd_echo(content, filename, append);} else {printf("echo: 缺少重定向目標文件。\n");}} else {printf("echo: 缺少操作數。\n");}} else if (strcmp(cmd, "cp") == 0) {if (arg_count > 2) {cmd_cp(args[1], args[2]);} else {printf("cp: 缺少操作數。\n");}} else if (strcmp(cmd, "mv") == 0) {if (arg_count > 2) {cmd_mv(args[1], args[2]);} else {printf("mv: 缺少操作數。\n");}} else if (strcmp(cmd, "chmod") == 0) {if (arg_count > 2) {cmd_chmod(args[1], args[2]);} else {printf("chmod: 缺少權限模式或文件操作數。\n");}}else {printf("sim_shell: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}// --- 主函數:模擬Shell環境 ---
int main() {printf("====== 簡易Linux Shell文件系統模擬器 (權限增強版) ======\n");printf("輸入 'exit' 退出。\n");// 初始化根目錄root_dir = create_node("/", NODE_TYPE_DIR, NULL);current_dir = root_dir;// 預設一些文件和目錄FileSystemNode* home = create_node("home", NODE_TYPE_DIR, root_dir);add_child(root_dir, home);FileSystemNode* user = create_node("user", NODE_TYPE_DIR, home);add_child(home, user);FileSystemNode* dev = create_node("dev", NODE_TYPE_DIR, root_dir);add_child(root_dir, dev);FileSystemNode* proc = create_node("proc", NODE_TYPE_DIR, root_dir);add_child(root_dir, proc);FileSystemNode* etc = create_node("etc", NODE_TYPE_DIR, root_dir);add_child(root_dir, etc);// 模擬一些文件FileSystemNode* readme = create_node("README.txt", NODE_TYPE_FILE, user);add_child(user, readme);cmd_echo("Welcome to the simulated Linux file system!", "README.txt", false); // 寫入內容readme->permissions = 0644; // 默認權限FileSystemNode* script = create_node("my_script.sh", NODE_TYPE_FILE, user);add_child(user, script);cmd_echo("#!/bin/bash\n", "my_script.sh", false);cmd_echo("echo \"Hello from script!\"", "my_script.sh", true);script->permissions = 0600; // 默認權限FileSystemNode* profile = create_node(".profile", NODE_TYPE_FILE, user); // 隱藏文件add_child(user, profile);cmd_echo("This is a hidden profile file.", ".profile", false);profile->permissions = 0600;char command_line[MAX_PATH_LEN];while (true) {printf("\nsim_shell@sim_linux:%s$ ", current_dir->name);if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break; // 讀取失敗或EOF}// 移除換行符command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_command(command_line);}printf("\n====== 模擬器退出,釋放內存 ======\n");free_node(root_dir); // 釋放所有文件系統節點內存return 0;
}
代碼分析與邏輯透析: 我們對之前的簡易Shell模擬器進行了增強,使其能夠模擬文件權限的修改和顯示。
FileSystemNode
結構體更新:新增了
uint16_t permissions;
字段,用于存儲文件的八進制權限(例如0755
)。新增了
char owner[MAX_NAME_LEN];
和char group[MAX_NAME_LEN];
字段,用于模擬文件所有者和所屬組。雖然本模擬中沒有實現用戶和組的切換,但結構已預留。
create_node
函數更新:在創建新節點時,會根據節點類型設置默認權限:目錄默認為
0755
(rwxr-xr-x),文件默認為0644
(rw-r--r--)。同時,將新節點的所有者和組設置為當前的模擬用戶和組 (
current_user
,current_group
)。
get_permission_string
輔助函數:這是將八進制權限數字轉換為
rwx
字符串表示的關鍵函數。它通過位運算 (
&
和? :
) 來判斷每個權限位(讀、寫、執行)是否被設置,并相應地填充r
、w
、x
或-
。例如,
perm & 0400
檢查所有者是否有讀權限,0400
的二進制是100 000 000
,只有當perm
的對應位是1時,結果才非零。
cmd_ls
函數更新:當使用
-l
參數時,它會調用get_permission_string
函數,將child->permissions
轉換為可讀的權限字符串,并在輸出中顯示。同時,也打印模擬的所有者和所屬組。
cmd_chmod
命令實現:接收兩個參數:權限模式字符串(例如
"755"
)和文件名。使用
sscanf(mode_str, "%o", &new_permissions)
:這是C語言中非常巧妙的用法!%o
格式說明符告訴sscanf
將輸入的字符串解析為一個八進制整數。這樣,用戶輸入的"755"
就會被正確地解析為八進制數0755
。進行簡單的權限范圍檢查(000-777)。
直接更新
target->permissions
字段,并更新文件的修改時間。打印出權限修改前后的字符串和八進制表示,方便用戶查看。
main
函數更新:在初始化文件系統時,為
README.txt
和my_script.sh
設置了初始權限,方便后續測試chmod
命令的效果。在命令解析部分,增加了對
chmod
命令的識別和調用。
通過這個增強版的模擬器,你可以:
直觀感受權限: 運行
ls -l
查看文件和目錄的權限字符串。親手修改權限: 嘗試使用
chmod 777 my_script.sh
,然后再次ls -l
,看看權限是否變化。理解八進制: 思考
755
、644
這些數字是如何對應rwx
權限組合的。
2.1.6 用戶與組(簡述):身份的象征
id
: 顯示當前用戶的用戶ID(UID)、組ID(GID)以及所屬的所有組。$ id uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),134(sambashare)
whoami
: 顯示當前有效用戶名。users
: 顯示當前登錄系統的所有用戶名。groups
: 顯示當前用戶所屬的所有組。
這些命令在D5的用戶管理部分會有更詳細的C語言模擬和講解。
2.2 輸入輸出重定向與管道:Shell的“乾坤大挪移”
在Linux Shell中,每個命令在執行時,默認都有三個標準I/O流:
標準輸入(stdin): 文件描述符為
0
,默認從鍵盤讀取輸入。標準輸出(stdout): 文件描述符為
1
,默認輸出到屏幕。標準錯誤(stderr): 文件描述符為
2
,默認輸出到屏幕(用于錯誤信息)。
輸入輸出重定向就是改變這些默認的I/O流向,讓命令的輸入來自文件,或將命令的輸出保存到文件。管道則是將一個命令的輸出作為另一個命令的輸入,實現命令的串聯。
2.2.1 輸出重定向:>
和 >>
>
:覆蓋重定向將命令的標準輸出重定向到文件。如果文件不存在則創建,如果文件已存在則覆蓋其內容。
ls -l > file_list.txt # 將ls -l的輸出保存到file_list.txt,如果文件存在則覆蓋 echo "Hello World" > hello.txt # 創建或覆蓋hello.txt,寫入"Hello World"
>>
:追加重定向將命令的標準輸出重定向到文件。如果文件不存在則創建,如果文件已存在則追加到文件末尾。
echo "First line" >> my_log.txt echo "Second line" >> my_log.txt # my_log.txt現在有兩行內容
2>
:標準錯誤重定向將命令的標準錯誤輸出重定向到文件。
find /nonexistent_dir 2> error.log # 將find命令的錯誤信息保存到error.log
&>
或>
&2`:標準輸出和標準錯誤同時重定向將標準輸出和標準錯誤都重定向到同一個文件。
command > output.log 2>&1 # 將標準輸出和標準錯誤都重定向到output.log command &> output.log # 簡化寫法,效果相同
2.2.2 輸入重定向:<
將命令的標準輸入重定向到文件。
sort < unsorted.txt > sorted.txt # 將unsorted.txt的內容作為sort命令的輸入,并將排序結果保存到sorted.txt
2.2.3 管道:|
將一個命令的標準輸出作為另一個命令的標準輸入。這使得復雜的任務可以通過多個簡單命令的組合來完成。
ls -l | grep "txt" # 列出當前目錄下所有文件和目錄的詳細信息,然后過濾出包含"txt"的行 cat access.log | grep "ERROR" | less # 查看日志文件中所有包含"ERROR"的行,并分頁顯示 ps aux | grep "nginx" | awk '{print $2}' # 查找nginx進程,并只顯示其PID
C語言模擬:管道(cat | grep
)
我們將模擬cat file | grep keyword
的邏輯。這需要兩個獨立的函數,一個模擬cat
將內容輸出到緩沖區,另一個模擬grep
從緩沖區讀取并過濾。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>// --- 宏定義 ---
#define MAX_LINE_LEN 256
#define MAX_BUFFER_SIZE 2048 // 模擬管道緩沖區大小// --- 模擬文件內容 ---
const char* simulated_file_content =
"This is line 1.\n"
"Hello, World!\n"
"This is line 3 with keyword.\n"
"Another line here.\n"
"Keyword found in line 5.\n"
"End of file.\n";// --- 函數:模擬 cat 命令 ---
// 實際cat命令會將文件內容輸出到stdout
// 在這里,我們模擬將內容寫入一個內存緩沖區,作為“管道”的輸出端
void sim_cat_to_buffer(char* output_buffer, int buffer_size) {printf("[模擬cat] 正在讀取文件內容并寫入緩沖區...\n");strncpy(output_buffer, simulated_file_content, buffer_size - 1);output_buffer[buffer_size - 1] = '\0'; // 確保字符串終止printf("[模擬cat] 文件內容已寫入緩沖區。\n");
}// --- 函數:模擬 grep 命令 ---
// 實際grep命令會從stdin讀取輸入,并將匹配行輸出到stdout
// 在這里,我們模擬從內存緩沖區讀取輸入,并將匹配行打印到屏幕
void sim_grep_from_buffer(const char* input_buffer, const char* keyword) {printf("[模擬grep] 正在從緩沖區讀取內容并查找關鍵字 '%s'...\n", keyword);char buffer_copy[MAX_BUFFER_SIZE];strncpy(buffer_copy, input_buffer, MAX_BUFFER_SIZE - 1);buffer_copy[MAX_BUFFER_SIZE - 1] = '\0';char* line = strtok(buffer_copy, "\n"); // 按行分割int line_num = 0;while (line != NULL) {line_num++;if (strstr(line, keyword) != NULL) { // 查找關鍵字printf("[模擬grep] 匹配到 (行 %d): %s\n", line_num, line);}line = strtok(NULL, "\n");}printf("[模擬grep] 關鍵字查找完成。\n");
}int main() {printf("====== C語言模擬管道 (cat | grep) ======\n");char pipe_buffer[MAX_BUFFER_SIZE]; // 模擬管道的內存緩沖區// 步驟1: 模擬 cat 命令將內容輸出到 pipe_buffersim_cat_to_buffer(pipe_buffer, sizeof(pipe_buffer));// 步驟2: 模擬 grep 命令從 pipe_buffer 讀取內容并查找 "keyword"sim_grep_from_buffer(pipe_buffer, "keyword");printf("\n====== 模擬結束 ======\n");return 0;
}
代碼分析與邏輯透析: 這個模擬器雖然簡單,但它清晰地展示了管道(|
)的工作原理:一個命令的輸出如何成為另一個命令的輸入。
simulated_file_content
: 這是一個常量字符串,模擬了cat
命令要讀取的“文件”內容。pipe_buffer
: 這是一個在main
函數中聲明的字符數組,它扮演了“管道”的角色。sim_cat_to_buffer
會將內容寫入這里,而sim_grep_from_buffer
會從這里讀取。sim_cat_to_buffer
函數:它模擬了
cat
命令的行為。在真實的Shell中,cat
會將文件內容打印到標準輸出(屏幕)。在這里,它不是打印到屏幕,而是將
simulated_file_content
的內容復制到output_buffer
中。這個output_buffer
就是我們模擬的“管道”的輸出端。
sim_grep_from_buffer
函數:它模擬了
grep
命令的行為。在真實的Shell中,grep
會從標準輸入讀取數據。在這里,它從
input_buffer
(也就是pipe_buffer
)中讀取數據。strtok(buffer_copy, "\n")
:這個函數用于將緩沖區內容按換行符分割成一行一行,模擬grep
逐行處理輸入。strstr(line, keyword)
:這個函數用于在每一行中查找指定的關鍵字。如果找到,就打印該行。
main
函數:首先調用
sim_cat_to_buffer
,將“文件”內容填充到pipe_buffer
中。然后,將
pipe_buffer
作為參數傳遞給sim_grep_from_buffer
。這完美地模擬了
cat file | grep keyword
的流程:cat
的輸出(pipe_buffer
)直接作為grep
的輸入,而不需要中間文件。
通過這個模擬,你就能理解,管道的本質就是將一個進程的標準輸出連接到另一個進程的標準輸入,通常通過內核的緩沖區來實現數據傳輸,從而實現高效的命令鏈式處理。
2.3 文件搜索與處理:Linux的“火眼金睛”
在Linux系統中,文件數量龐大,如何快速找到你需要的文件,并對其進行處理,是高效工作的基礎。
2.3.1 find
:文件世界的“偵察兵”
find
命令用于在指定目錄下搜索文件和目錄。它非常強大,可以根據各種條件進行搜索。
基本用法:
find <路徑> <表達式>
find . -name "*.txt" # 在當前目錄及其子目錄下查找所有以.txt結尾的文件 find /home -type d # 在/home目錄下查找所有目錄 find . -size +1M # 查找大于1MB的文件 find . -mtime -7 # 查找7天內修改過的文件 find . -user user1 # 查找所有者為user1的文件 find . -perm 644 # 查找權限為644的文件 find . -name "temp*" -delete # 查找以temp開頭的文件并刪除 (危險操作,慎用!) find . -name "*.log" -exec rm {} \; # 查找所有.log文件并刪除 ({}代表find找到的文件名,\;表示exec命令結束)
2.3.2 grep
:文本內容的“過濾器”
grep
(global regular expression print) 命令用于在文件中搜索匹配指定模式(正則表達式)的行。
基本用法:
grep <模式> <文件>
grep "error" /var/log/syslog # 在syslog文件中查找包含"error"的行 grep -i "warning" my_app.log # 不區分大小寫查找"warning" grep -n "fail" debug.log # 顯示匹配行的行號 grep -v "info" access.log # 顯示不包含"info"的行 grep -r "TODO" ./src # 遞歸地在src目錄及其子文件查找包含"TODO"的行 grep "^[0-9]" data.txt # 查找以數字開頭的行 (正則表達式)
2.3.3 diff
:文件內容的“找茬專家”
diff
(difference) 命令用于比較兩個文件或目錄之間的差異。
基本用法:
diff <文件1> <文件2>
diff file1.txt file2.txt # 比較兩個文件,顯示差異 diff -u old_code.c new_code.c # 以統一格式顯示差異 (常用于生成補丁文件)
2.3.4 sort
:文本內容的“排序大師”
sort
命令用于對文本文件的行進行排序。
基本用法:
sort <文件>
sort names.txt # 按字母順序排序names.txt sort -r numbers.txt # 逆序排序 sort -n data.txt # 按數值大小排序 sort -k 2 data.txt # 按第二列排序
2.3.5 uniq
:重復行的“去重神器”
uniq
命令用于報告或忽略文件中的重復行。通常與sort
命令結合使用。
基本用法:
uniq <文件>
sort data.txt | uniq # 排序后去除重復行 sort data.txt | uniq -c # 統計每行重復的次數
2.3.6 wc
:字數統計的“計數器”
wc
(word count) 命令用于統計文件中的行數、單詞數和字節數。
基本用法:
wc <文件>
wc my_document.txt # 統計行數、單詞數、字節數 wc -l my_document.txt # 只統計行數 wc -w my_document.txt # 只統計單詞數 wc -c my_document.txt # 只統計字節數
C語言模擬:find
與grep
的組合(模擬find . -name "*.txt" -exec grep "keyword" {} \;
)
我們將結合之前的文件系統模擬器,實現一個能夠遞歸查找文件并搜索文件內容的組合功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h> // 用于模擬時間戳// --- 宏定義:文件系統限制 (與之前一致) ---
#define MAX_NAME_LEN 64
#define MAX_CHILDREN 10
#define MAX_PATH_LEN 256
#define MAX_FILE_CONTENT_LEN 1024// --- 枚舉:節點類型 (與之前一致) ---
typedef enum {NODE_TYPE_DIR,NODE_TYPE_FILE
} NodeType;// --- 結構體:文件/目錄節點 (與之前一致,權限等字段省略以簡化) ---
typedef struct FileSystemNode {char name[MAX_NAME_LEN];NodeType type;time_t creation_time;time_t modification_time;uint16_t permissions; // 權限 (例如:0755,八進制表示)char owner[MAX_NAME_LEN]; // 所有者 (模擬)char group[MAX_NAME_LEN]; // 所屬組 (模擬)struct FileSystemNode* parent;union {struct {struct FileSystemNode* children[MAX_CHILDREN];int num_children;} dir;struct {char content[MAX_FILE_CONTENT_LEN];int content_len;} file;} data;
} FileSystemNode;// --- 全局變量:模擬文件系統根目錄和當前工作目錄 (與之前一致) ---
FileSystemNode* root_dir = NULL;
FileSystemNode* current_dir = NULL;
char current_user[MAX_NAME_LEN] = "sim_user";
char current_group[MAX_NAME_LEN] = "sim_group";// --- 輔助函數:創建新節點 (與之前一致) ---
FileSystemNode* create_node(const char* name, NodeType type, FileSystemNode* parent) {FileSystemNode* new_node = (FileSystemNode*)malloc(sizeof(FileSystemNode));if (new_node == NULL) {fprintf(stderr, "內存分配失敗!\n");exit(EXIT_FAILURE);}strncpy(new_node->name, name, MAX_NAME_LEN - 1);new_node->name[MAX_NAME_LEN - 1] = '\0';new_node->type = type;new_node->creation_time = time(NULL);new_node->modification_time = time(NULL);new_node->permissions = (type == NODE_TYPE_DIR) ? 0755 : 0644; // 默認權限strncpy(new_node->owner, current_user, MAX_NAME_LEN - 1);new_node->owner[MAX_NAME_LEN - 1] = '\0';strncpy(new_node->group, current_group, MAX_NAME_LEN - 1);new_node->group[MAX_NAME_LEN - 1] = '\0';new_node->parent = parent;if (type == NODE_TYPE_DIR) {new_node->data.dir.num_children = 0;for (int i = 0; i < MAX_CHILDREN; i++) {new_node->data.dir.children[i] = NULL;}} else { // NODE_TYPE_FILEnew_node->data.file.content[0] = '\0';new_node->data.file.content_len = 0;}return new_node;
}// --- 輔助函數:查找子節點 (與之前一致) ---
FileSystemNode* find_child(FileSystemNode* parent, const char* name) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return NULL;for (int i = 0; i < parent->data.dir.num_children; i++) {if (strcmp(parent->data.dir.children[i]->name, name) == 0) {return parent->data.dir.children[i];}}return NULL;
}// --- 輔助函數:添加子節點 (與之前一致) ---
bool add_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR) return false;if (parent->data.dir.num_children >= MAX_CHILDREN) {fprintf(stderr, "目錄 %s 子節點已滿。\n", parent->name);return false;}parent->data.dir.children[parent->data.dir.num_children++] = child;child->parent = parent; // 確保子節點的父指針正確parent->modification_time = time(NULL); // 更新父目錄修改時間return true;
}// --- 輔助函數:移除子節點 (與之前一致) ---
bool remove_child(FileSystemNode* parent, FileSystemNode* child) {if (parent == NULL || parent->type != NODE_TYPE_DIR || child == NULL) return false;for (int i = 0; i < parent->data.dir.num_children; i++) {if (parent->data.dir.children[i] == child) {parent->data.dir.children[i] = parent->data.dir.children[parent->data.dir.num_children - 1];parent->data.dir.children[parent->data.dir.num_children - 1] = NULL;parent->data.dir.num_children--;parent->modification_time = time(NULL);return true;}}return false;
}// --- 輔助函數:遞歸釋放文件系統節點內存 (與之前一致) ---
void free_node(FileSystemNode* node) {if (node == NULL) return;if (node->type == NODE_TYPE_DIR) {for (int i = 0; i < node->data.dir.num_children; i++) {free_node(node->data.dir.children[i]);}}free(node);
}// --- 輔助函數:將八進制權限轉換為rwx字符串 (與之前一致) ---
void get_permission_string(uint16_t perm, char* perm_str) {perm_str[0] = (perm & 0400) ? 'r' : '-';perm_str[1] = (perm & 0200) ? 'w' : '-';perm_str[2] = (perm & 0100) ? 'x' : '-';perm_str[3] = (perm & 0040) ? 'r' : '-';perm_str[4] = (perm & 0020) ? 'w' : '-';perm_str[5] = (perm & 0010) ? 'x' : '-';perm_str[6] = (perm & 0004) ? 'r' : '-';perm_str[7] = (perm & 0002) ? 'w' : '-';perm_str[8] = (perm & 0001) ? 'x' : '-';perm_str[9] = '\0';
}// --- 輔助函數:獲取節點完整路徑 ---
void get_full_path(FileSystemNode* node, char* path_buffer) {if (node == NULL) {strcpy(path_buffer, "/");return;}char temp_path[MAX_PATH_LEN];temp_path[0] = '\0';FileSystemNode* temp = node;while (temp != NULL) {char current_segment[MAX_NAME_LEN + 1];if (temp->parent == NULL) { // 根目錄strncat(temp_path, "/", MAX_PATH_LEN - strlen(temp_path) - 1);break;}snprintf(current_segment, sizeof(current_segment), "/%s", temp->name);strncat(current_segment, temp_path, MAX_PATH_LEN - strlen(current_segment) - 1);strncpy(temp_path, current_segment, MAX_PATH_LEN - 1);temp_path[MAX_PATH_LEN - 1] = '\0';temp = temp->parent;}strcpy(path_buffer, temp_path);
}// --- 命令實現:ls (與之前一致) ---
void cmd_ls(FileSystemNode* dir, bool show_all, bool long_format) {if (dir == NULL || dir->type != NODE_TYPE_DIR) {printf("ls: 無法訪問目錄。\n");return;}printf("目錄 '%s' 的內容:\n", dir->name);for (int i = 0; i < dir->data.dir.num_children; i++) {FileSystemNode* child = dir->data.dir.children[i];if (child == NULL) continue;if (!show_all && child->name[0] == '.') {continue;}if (long_format) {char time_str[20];strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", localtime(&child->modification_time));char perm_str[10];get_permission_string(child->permissions, perm_str);printf("%c%s %s %s %s %s %s\n",(child->type == NODE_TYPE_DIR) ? 'd' : '-',perm_str,child->owner,child->group,time_str,(child->type == NODE_TYPE_FILE) ? "FILE" : "DIR",child->name);} else {printf("%s\n", child->name);}}
}// --- 命令實現:pwd (與之前一致) ---
void cmd_pwd(FileSystemNode* node) {char path_buffer[MAX_PATH_LEN];get_full_path(node, path_buffer);printf("%s\n", path_buffer);
}// --- 命令實現:cd (與之前一致) ---
void cmd_cd(const char* path) {if (strcmp(path, "/") == 0) {current_dir = root_dir;printf("cd: 已切換到根目錄。\n");return;}if (strcmp(path, "..") == 0) {if (current_dir->parent != NULL) {current_dir = current_dir->parent;printf("cd: 已切換到上級目錄。\n");} else {printf("cd: 已經是根目錄。\n");}return;}if (strcmp(path, ".") == 0) {printf("cd: 仍在當前目錄。\n");return;}FileSystemNode* target_dir = find_child(current_dir, path);if (target_dir != NULL && target_dir->type == NODE_TYPE_DIR) {current_dir = target_dir;printf("cd: 已切換到目錄 '%s'。\n", path);} else {printf("cd: 目錄 '%s' 不存在或不是目錄。\n", path);}
}// --- 命令實現:mkdir (與之前一致) ---
void cmd_mkdir(const char* name) {if (find_child(current_dir, name) != NULL) {printf("mkdir: 目錄 '%s' 已存在。\n", name);return;}FileSystemNode* new_dir = create_node(name, NODE_TYPE_DIR, current_dir);if (add_child(current_dir, new_dir)) {printf("mkdir: 目錄 '%s' 創建成功。\n", name);} else {free_node(new_dir);}
}// --- 命令實現:rm (與之前一致) ---
void cmd_rm(const char* name, bool recursive) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("rm: '%s' 不存在。\n", name);return;}if (target->type == NODE_TYPE_DIR && !recursive) {printf("rm: 無法刪除目錄 '%s',請使用 -r 選項。\n", name);return;}if (target->type == NODE_TYPE_DIR && target->data.dir.num_children > 0 && recursive) {printf("rm: 正在遞歸刪除目錄 '%s'...\n", name);}if (remove_child(current_dir, target)) {free_node(target);printf("rm: '%s' 刪除成功。\n", name);} else {printf("rm: 刪除 '%s' 失敗。\n", name);}
}// --- 命令實現:touch (與之前一致) ---
void cmd_touch(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target != NULL) {target->modification_time = time(NULL);printf("touch: 文件 '%s' 時間戳已更新。\n", name);return;}FileSystemNode* new_file = create_node(name, NODE_TYPE_FILE, current_dir);if (add_child(current_dir, new_file)) {printf("touch: 文件 '%s' 創建成功。\n", name);} else {free_node(new_file);}
}// --- 命令實現:cat (與之前一致) ---
void cmd_cat(const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("cat: '%s' 不存在。\n", name);return;}if (target->type != NODE_TYPE_FILE) {printf("cat: '%s' 不是文件。\n", name);return;}printf("--- 文件 '%s' 內容 ---\n", name);printf("%s\n", target->data.file.content);printf("------------------------\n");
}// --- 命令實現:echo (與之前一致) ---
void cmd_echo(const char* content, const char* filename, bool append) {FileSystemNode* target = find_child(current_dir, filename);if (target == NULL) {target = create_node(filename, NODE_TYPE_FILE, current_dir);if (!add_child(current_dir, target)) {free_node(target);printf("echo: 無法創建文件 '%s'。\n", filename);return;}printf("echo: 文件 '%s' 創建成功。\n", filename);} else if (target->type != NODE_TYPE_FILE) {printf("echo: '%s' 不是文件。\n", filename);return;}if (append) {int current_len = target->data.file.content_len;int content_to_add_len = strlen(content);if (current_len + content_to_add_len + 1 > MAX_FILE_CONTENT_LEN) {printf("echo: 文件 '%s' 內容超出最大限制。\n", filename);return;}strcat(target->data.file.content, content);target->data.file.content_len += content_to_add_len;printf("echo: 內容已追加到文件 '%s'。\n", filename);} else {strncpy(target->data.file.content, content, MAX_FILE_CONTENT_LEN - 1);target->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';target->data.file.content_len = strlen(content);printf("echo: 內容已寫入文件 '%s'。\n", filename);}target->modification_time = time(NULL);
}// --- 命令實現:cp (與之前一致) ---
void cmd_cp(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("cp: 源文件或目錄 '%s' 不存在。\n", source_name);return;}if (source->type == NODE_TYPE_DIR) {printf("cp: 暫不支持目錄復制,請使用 -r 選項 (本模擬未實現)。\n");return;}FileSystemNode* dest = find_child(current_dir, dest_name);if (dest != NULL && dest->type == NODE_TYPE_DIR) {FileSystemNode* new_file = create_node(source->name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(dest, new_file)) {printf("cp: 文件 '%s' 已復制到目錄 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 到目錄 '%s' 失敗。\n", source_name, dest_name);}} else if (dest != NULL && dest->type == NODE_TYPE_FILE) {strncpy(dest->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);dest->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';dest->data.file.content_len = source->data.file.content_len;dest->modification_time = time(NULL);printf("cp: 文件 '%s' 已覆蓋文件 '%s'。\n", source_name, dest_name);} else {FileSystemNode* new_file = create_node(dest_name, NODE_TYPE_FILE, NULL);strncpy(new_file->data.file.content, source->data.file.content, MAX_FILE_CONTENT_LEN - 1);new_file->data.file.content[MAX_FILE_CONTENT_LEN - 1] = '\0';new_file->data.file.content_len = source->data.file.content_len;if (add_child(current_dir, new_file)) {printf("cp: 文件 '%s' 已復制為 '%s'。\n", source_name, dest_name);} else {free_node(new_file);printf("cp: 復制文件 '%s' 失敗。\n", source_name);}}
}// --- 命令實現:mv (與之前一致) ---
void cmd_mv(const char* source_name, const char* dest_name) {FileSystemNode* source = find_child(current_dir, source_name);if (source == NULL) {printf("mv: 源文件或目錄 '%s' 不存在。\n", source_name);return;}FileSystemNode* dest_parent = current_dir;char actual_dest_name[MAX_NAME_LEN];strncpy(actual_dest_name, dest_name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';FileSystemNode* dest_node = find_child(current_dir, dest_name);if (dest_node != NULL && dest_node->type == NODE_TYPE_DIR) {dest_parent = dest_node;strncpy(actual_dest_name, source->name, MAX_NAME_LEN - 1);actual_dest_name[MAX_NAME_LEN - 1] = '\0';if (find_child(dest_parent, actual_dest_name) != NULL) {printf("mv: 目標目錄 '%s' 中已存在同名文件或目錄 '%s'。\n", dest_name, actual_dest_name);return;}} else if (dest_node != NULL && dest_node->type == NODE_TYPE_FILE) {if (remove_child(current_dir, dest_node)) {free_node(dest_node);printf("mv: 目標文件 '%s' 已被覆蓋。\n", dest_name);} else {printf("mv: 無法覆蓋目標文件 '%s'。\n", dest_name);return;}} else {}if (remove_child(current_dir, source)) {strncpy(source->name, actual_dest_name, MAX_NAME_LEN - 1);source->name[MAX_NAME_LEN - 1] = '\0';if (add_child(dest_parent, source)) {printf("mv: '%s' 已移動/重命名為 '%s'。\n", source_name, dest_name);} else {fprintf(stderr, "mv: 移動/重命名失敗,數據可能丟失。\n");}} else {printf("mv: 無法從當前目錄移除 '%s'。\n", source_name);}
}// --- 命令實現:chmod (與之前一致) ---
void cmd_chmod(const char* mode_str, const char* name) {FileSystemNode* target = find_child(current_dir, name);if (target == NULL) {printf("chmod: '%s' 不存在。\n", name);return;}int new_permissions = 0;if (sscanf(mode_str, "%o", &new_permissions) != 1) {printf("chmod: 無效的權限模式 '%s'。請使用三位八進制數 (例如: 755)。\n", mode_str);return;}if (new_permissions < 0 || new_permissions > 0777) {printf("chmod: 權限模式 '%s' 超出有效范圍 (000-777)。\n", mode_str);return;}uint16_t old_permissions = target->permissions;target->permissions = (uint16_t)new_permissions;target->modification_time = time(NULL);char old_perm_str[10], new_perm_str[10];get_permission_string(old_permissions, old_perm_str);get_permission_string(new_permissions, new_perm_str);printf("chmod: '%s' 的權限已從 %s (%03o) 更改為 %s (%03o)。\n",name, old_perm_str, old_permissions, new_perm_str, new_permissions);
}// --- 命令實現:grep (模擬在文件內容中搜索) ---
void cmd_grep(const char* pattern, const char* filename) {FileSystemNode* target = find_child(current_dir, filename);if (target == NULL) {printf("grep: '%s' 不存在。\n", filename);return;}if (target->type != NODE_TYPE_FILE) {printf("grep: '%s' 不是文件。\n", filename);return;}printf("--- 在文件 '%s' 中搜索 '%s' ---\n", filename, pattern);char* content_copy = strdup(target->data.file.content); // 復制內容,因為strtok會修改if (content_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}char* line = strtok(content_copy, "\n");int line_num = 0;bool found = false;while (line != NULL) {line_num++;if (strstr(line, pattern) != NULL) {printf("%s:%d:%s\n", filename, line_num, line);found = true;}line = strtok(NULL, "\n");}if (!found) {printf("未找到匹配項。\n");}free(content_copy);printf("-------------------------------\n");
}// --- 命令實現:find (模擬遞歸搜索文件) ---
// 輔助函數:遞歸查找
void find_recursive(FileSystemNode* node, const char* name_pattern, const char* grep_pattern, char* current_path_prefix) {if (node == NULL) return;char full_path[MAX_PATH_LEN];snprintf(full_path, MAX_PATH_LEN, "%s%s%s", current_path_prefix, (strcmp(current_path_prefix, "/") == 0 ? "" : "/"), node->name);if (node->type == NODE_TYPE_FILE) {// 檢查文件名是否匹配if (name_pattern == NULL || strstr(node->name, name_pattern) != NULL) {printf("%s\n", full_path); // 打印找到的文件// 如果有grep模式,則對文件內容進行grepif (grep_pattern != NULL) {char* content_copy = strdup(node->data.file.content);if (content_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}char* line = strtok(content_copy, "\n");int line_num = 0;while (line != NULL) {line_num++;if (strstr(line, grep_pattern) != NULL) {printf(" -> 匹配內容: %s:%d:%s\n", full_path, line_num, line);}line = strtok(NULL, "\n");}free(content_copy);}}} else if (node->type == NODE_TYPE_DIR) {// 如果是目錄,遞歸查找其子節點for (int i = 0; i < node->data.dir.num_children; i++) {find_recursive(node->data.dir.children[i], name_pattern, grep_pattern, full_path);}}
}void cmd_find(const char* start_path_str, const char* name_pattern, const char* grep_pattern) {printf("\n--- 模擬 find 命令 ---\n");FileSystemNode* start_node = NULL;if (strcmp(start_path_str, ".") == 0) {start_node = current_dir;} else if (strcmp(start_path_str, "/") == 0) {start_node = root_dir;} else {// 簡化:只支持當前目錄下的子目錄或文件作為起始路徑start_node = find_child(current_dir, start_path_str);if (start_node == NULL) {printf("find: 起始路徑 '%s' 不存在。\n", start_path_str);return;}}char initial_path_prefix[MAX_PATH_LEN];get_full_path(start_node, initial_path_prefix);printf("在 '%s' 中查找 (名稱模式: '%s', 內容模式: '%s')...\n", initial_path_prefix, name_pattern ? name_pattern : "無", grep_pattern ? grep_pattern : "無");// 針對起始節點本身進行檢查,因為遞歸函數從子節點開始if (start_node->type == NODE_TYPE_FILE) {if (name_pattern == NULL || strstr(start_node->name, name_pattern) != NULL) {printf("%s\n", initial_path_prefix);if (grep_pattern != NULL) {char* content_copy = strdup(start_node->data.file.content);if (content_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}char* line = strtok(content_copy, "\n");int line_num = 0;while (line != NULL) {line_num++;if (strstr(line, grep_pattern) != NULL) {printf(" -> 匹配內容: %s:%d:%s\n", initial_path_prefix, line_num, line);}line = strtok(NULL, "\n");}free(content_copy);}}} else { // 如果是目錄,則遞歸其所有子節點for (int i = 0; i < start_node->data.dir.num_children; i++) {find_recursive(start_node->data.dir.children[i], name_pattern, grep_pattern, initial_path_prefix);}}printf("------------------------\n");
}// --- 命令解析器 (更新以支持 grep 和 find) ---
void parse_and_execute_command(char* command_line) {char* token;char* args[MAX_CHILDREN + 1];int arg_count = 0;char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n");while (token != NULL && arg_count < MAX_CHILDREN + 1) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}args[arg_count] = NULL;if (arg_count == 0) {free(cmd_copy);return;}const char* cmd = args[0];if (strcmp(cmd, "ls") == 0) {bool show_all = false;bool long_format = false;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-a") == 0) show_all = true;if (strcmp(args[i], "-l") == 0) long_format = true;if (strcmp(args[i], "-lh") == 0) { long_format = true; }}cmd_ls(current_dir, show_all, long_format);} else if (strcmp(cmd, "pwd") == 0) {cmd_pwd(current_dir);} else if (strcmp(cmd, "cd") == 0) {if (arg_count > 1) {cmd_cd(args[1]);} else {cmd_cd("~");}} else if (strcmp(cmd, "mkdir") == 0) {if (arg_count > 1) {cmd_mkdir(args[1]);} else {printf("mkdir: 缺少操作數。\n");}} else if (strcmp(cmd, "rm") == 0) {if (arg_count > 1) {bool recursive = false;char* target_name = NULL;for (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-r") == 0 || strcmp(args[i], "-rf") == 0) {recursive = true;} else {target_name = args[i];}}if (target_name) {cmd_rm(target_name, recursive);} else {printf("rm: 缺少操作數。\n");}} else {printf("rm: 缺少操作數。\n");}} else if (strcmp(cmd, "touch") == 0) {if (arg_count > 1) {cmd_touch(args[1]);} else {printf("touch: 缺少操作數。\n");}} else if (strcmp(cmd, "cat") == 0) {if (arg_count > 1) {cmd_cat(args[1]);} else {printf("cat: 缺少操作數。\n");}} else if (strcmp(cmd, "echo") == 0) {if (arg_count > 2) {bool append = false;char* content = args[1];char* filename = NULL;for (int i = 2; i < arg_count; i++) {if (strcmp(args[i], ">") == 0) {if (i + 1 < arg_count) filename = args[i+1];break;} else if (strcmp(args[i], ">>") == 0) {append = true;if (i + 1 < arg_count) filename = args[i+1];break;}}if (filename) {cmd_echo(content, filename, append);} else {printf("echo: 缺少重定向目標文件。\n");}} else {printf("echo: 缺少操作數。\n");}} else if (strcmp(cmd, "cp") == 0) {if (arg_count > 2) {cmd_cp(args[1], args[2]);} else {printf("cp: 缺少操作數。\n");}} else if (strcmp(cmd, "mv") == 0) {if (arg_count > 2) {cmd_mv(args[1], args[2]);} else {printf("mv: 缺少操作數。\n");}} else if (strcmp(cmd, "chmod") == 0) {if (arg_count > 2) {cmd_chmod(args[1], args[2]);} else {printf("chmod: 缺少權限模式或文件操作數。\n");}} else if (strcmp(cmd, "grep") == 0) {if (arg_count > 2) {cmd_grep(args[1], args[2]);} else {printf("grep: 缺少模式或文件操作數。\n");}} else if (strcmp(cmd, "find") == 0) {char* start_path = "."; // 默認當前目錄char* name_pattern = NULL;char* grep_pattern = NULL;// 簡化參數解析,只支持 -name 和 -exec grepfor (int i = 1; i < arg_count; i++) {if (strcmp(args[i], "-name") == 0) {if (i + 1 < arg_count) {name_pattern = args[++i];} else {printf("find: -name 缺少參數。\n");free(cmd_copy); return;}} else if (strcmp(args[i], "-exec") == 0) {if (i + 1 < arg_count && strcmp(args[i+1], "grep") == 0) {if (i + 2 < arg_count) {grep_pattern = args[i+2];i += 2; // 跳過 grep 和 pattern} else {printf("find: -exec grep 缺少模式。\n");free(cmd_copy); return;}} else {printf("find: -exec 暫不支持非 grep 命令。\n");free(cmd_copy); return;}} else {start_path = args[i]; // 第一個非選項參數作為起始路徑}}cmd_find(start_path, name_pattern, grep_pattern);}else {printf("sim_shell: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}// --- 主函數:模擬Shell環境 (更新以包含更多文件) ---
int main() {printf("====== 簡易Linux Shell文件系統模擬器 (增強版) ======\n");printf("輸入 'exit' 退出。\n");// 初始化根目錄root_dir = create_node("/", NODE_TYPE_DIR, NULL);current_dir = root_dir;// 預設一些文件和目錄FileSystemNode* home = create_node("home", NODE_TYPE_DIR, root_dir);add_child(root_dir, home);FileSystemNode* user = create_node("user", NODE_TYPE_DIR, home);add_child(home, user);FileSystemNode* dev = create_node("dev", NODE_TYPE_DIR, root_dir);add_child(root_dir, dev);FileSystemNode* proc = create_node("proc", NODE_TYPE_DIR, root_dir);add_child(root_dir, proc);FileSystemNode* etc = create_node("etc", NODE_TYPE_DIR, root_dir);add_child(root_dir, etc);// 模擬一些文件FileSystemNode* readme = create_node("README.txt", NODE_TYPE_FILE, user);add_child(user, readme);cmd_echo("Welcome to the simulated Linux file system!", "README.txt", false);readme->permissions = 0644;FileSystemNode* script = create_node("my_script.sh", NODE_TYPE_FILE, user);add_child(user, script);cmd_echo("#!/bin/bash\n", "my_script.sh", false);cmd_echo("echo \"Hello from script! This is a test.\"", "my_script.sh", true);script->permissions = 0755; // 可執行權限FileSystemNode* log_file = create_node("app.log", NODE_TYPE_FILE, user);add_child(user, log_file);cmd_echo("INFO: Application started.\n", "app.log", false);cmd_echo("WARNING: Low disk space.\n", "app.log", true);cmd_echo("ERROR: Failed to connect to database.\n", "app.log", true);cmd_echo("INFO: User logged in.\n", "app.log", true);log_file->permissions = 0640;FileSystemNode* config_file = create_node("config.ini", NODE_TYPE_FILE, user);add_child(user, config_file);cmd_echo("[General]\n", "config.ini", false);cmd_echo("LogLevel=INFO\n", "config.ini", true);cmd_echo("Port=8080\n", "config.ini", true);config_file->permissions = 0600;FileSystemNode* hidden_file = create_node(".hidden_config", NODE_TYPE_FILE, user);add_child(user, hidden_file);cmd_echo("secret_key=12345", ".hidden_config", false);hidden_file->permissions = 0600;// 創建一個子目錄FileSystemNode* docs_dir = create_node("docs", NODE_TYPE_DIR, user);add_child(user, docs_dir);docs_dir->permissions = 0755;FileSystemNode* doc1 = create_node("document1.txt", NODE_TYPE_FILE, docs_dir);add_child(docs_dir, doc1);cmd_echo("This is the first document.", "document1.txt", false);doc1->permissions = 0644;FileSystemNode* doc2 = create_node("document2.md", NODE_TYPE_FILE, docs_dir);add_child(docs_dir, doc2);cmd_echo("This is the second document with some important info.", "document2.md", false);doc2->permissions = 0644;char command_line[MAX_PATH_LEN];while (true) {printf("\nsim_shell@sim_linux:%s$ ", current_dir->name);if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break;}command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_command(command_line);}printf("\n====== 模擬器退出,釋放內存 ======\n");free_node(root_dir);return 0;
}
代碼分析與邏輯透析: 這個C語言模擬器在之前的基礎上,進一步增強了文件系統功能,加入了grep
和find
命令的模擬,讓你能更深入地理解這些復雜命令的內部工作原理。
get_full_path
輔助函數:這是一個新的輔助函數,用于根據給定的
FileSystemNode
,遞歸地向上回溯到根目錄,構建出該節點的完整絕對路徑。這對于
find
命令在輸出結果時顯示完整路徑非常重要。
cmd_grep
命令實現:接收
pattern
(要搜索的關鍵字)和filename
作為參數。首先通過
find_child
找到目標文件。然后,它復制文件的內容到
content_copy
(因為strtok
會修改原字符串)。使用
strtok(content_copy, "\n")
逐行分割文件內容。對于每一行,使用
strstr(line, pattern)
來檢查該行是否包含關鍵字。如果匹配,則打印出
文件名:行號:行內容
的格式,模擬真實grep
的輸出。
find_recursive
輔助函數:這是
cmd_find
命令的核心,它是一個遞歸函數,用于遍歷文件系統樹。node
:當前正在訪問的節點。name_pattern
:要匹配的文件名模式(如果為NULL
則不按文件名過濾)。grep_pattern
:要搜索的文件內容模式(如果為NULL
則不對內容進行grep
)。current_path_prefix
:當前節點所在的父目錄的完整路徑,用于構建當前節點的完整路徑。遞歸邏輯:
如果當前節點是文件 (
NODE_TYPE_FILE
):檢查文件名是否匹配
name_pattern
。如果匹配,打印文件的完整路徑。
如果同時提供了
grep_pattern
,則調用strstr
對文件內容進行搜索,并打印匹配到的行。
如果當前節點是目錄 (
NODE_TYPE_DIR
):遍歷其所有子節點,并對每個子節點遞歸調用
find_recursive
函數。
通過這種遞歸,
find
命令能夠遍歷整個文件系統樹。
cmd_find
命令實現:接收
start_path_str
(搜索起始路徑)、name_pattern
(文件名模式)和grep_pattern
(內容模式)作為參數。解析命令行參數,支持
-name
和-exec grep
的簡化形式。確定搜索的起始節點。
調用
find_recursive
函數,從起始節點開始進行遞歸搜索。
main
函數更新:在文件系統初始化時,添加了更多不同類型的文件(
app.log
,config.ini
,.hidden_config
)和一個子目錄(docs
),并在其中放置了文件,以便更好地測試grep
和find
命令。在命令解析部分,增加了對
grep
和find
命令的識別和調用。
通過這個模擬器,你可以:
體驗
grep
: 嘗試grep ERROR app.log
或grep info config.ini
,看看它如何過濾文件內容。感受
find
的強大: 嘗試find . -name "*.txt"
來查找所有txt文件。組合使用: 嘗試
find . -name "*.log" -exec grep "WARNING" {} \;
來模擬查找所有日志文件并過濾出警告信息。理解遞歸: 思考
find_recursive
函數是如何通過遞歸調用來遍歷整個文件系統樹的。
2.4 壓縮與解壓:文件的“縮骨功”
在Linux中,為了節省存儲空間和方便傳輸,我們經常需要對文件進行壓縮和解壓。
2.4.1 常見的壓縮工具:gzip
, bzip2
, xz
這些工具通常用于壓縮單個文件,或者與tar
命令結合使用。
gzip
:最常用的壓縮工具之一,壓縮速度快,但壓縮率相對較低。
壓縮后文件擴展名為
.gz
。
gzip file.txt # 壓縮file.txt為file.txt.gz,原文件會被刪除 gzip -d file.txt.gz # 解壓file.txt.gz為file.txt gunzip file.txt.gz # 等同于gzip -d gzip -c file.txt > file.txt.gz # 壓縮并保留原文件
bzip2
:壓縮率比
gzip
高,但壓縮和解壓速度較慢。壓縮后文件擴展名為
.bz2
。
bzip2 file.txt # 壓縮file.txt為file.txt.bz2 bzip2 -d file.txt.bz2 # 解壓 bunzip2 file.txt.bz2 # 等同于bzip2 -d
xz
:壓縮率最高,但壓縮和解壓速度最慢,對CPU資源消耗較大。
壓縮后文件擴展名為
.xz
。
xz file.txt # 壓縮file.txt為file.txt.xz xz -d file.txt.xz # 解壓 unxz file.txt.xz # 等同于xz -d
2.4.2 打包與壓縮的利器:tar
tar
(tape archive) 命令最初用于磁帶歸檔,現在主要用于將多個文件或目錄打包成一個文件,通常再結合壓縮工具進行壓縮。
基本用法:
打包:
tar -cvf <歸檔文件名.tar> <文件/目錄1> [文件/目錄2 ...]
c
:創建歸檔文件v
:顯示詳細信息f
:指定歸檔文件名
解包:
tar -xvf <歸檔文件名.tar>
x
:解開歸檔文件
查看內容:
tar -tvf <歸檔文件名.tar>
t
:列出歸檔文件內容
打包并壓縮(常用):
.tar.gz
或.tgz
(使用gzip壓縮):tar -czvf archive.tar.gz folder/ file.txt # 打包并用gzip壓縮 tar -xzvf archive.tar.gz # 解壓
z
:通過gzip進行壓縮/解壓
.tar.bz2
或.tbz2
(使用bzip2壓縮):tar -cjvf archive.tar.bz2 folder/ file.txt # 打包并用bzip2壓縮 tar -xjvf archive.tar.bz2 # 解壓
j
:通過bzip2進行壓縮/解壓
.tar.xz
或.txz
(使用xz壓縮):tar -cJvf archive.tar.xz folder/ file.txt # 打包并用xz壓縮 (注意是大寫J) tar -xJvf archive.tar.xz # 解壓 (注意是大寫J)
J
:通過xz進行壓縮/解壓
解壓到指定目錄:
tar -xzvf archive.tar.gz -C /tmp/new_dir # 解壓到/tmp/new_dir目錄
2.4.3 zip
與 unzip
:跨平臺通用壓縮
zip
和unzip
是Windows和Linux都支持的通用壓縮格式,方便跨平臺傳輸。
zip
:zip archive.zip file1.txt file2.txt folder/ # 壓縮文件和目錄 zip -r archive.zip folder/ # 遞歸壓縮目錄
unzip
:unzip archive.zip # 解壓到當前目錄 unzip archive.zip -d /tmp/ # 解壓到指定目錄
C語言模擬:簡易的Run-Length Encoding (RLE) 壓縮與解壓
雖然C語言實現復雜的gzip
或tar
非常困難,但我們可以模擬一個最簡單的壓縮算法——Run-Length Encoding (RLE),來理解壓縮的本質。RLE適用于連續重復字符較多的數據。
原理: 將連續重復的字符替換為“重復次數 + 字符”的形式。
例如:
AAABBC
壓縮為3A2B1C
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // For malloc, free// --- 宏定義 ---
#define MAX_INPUT_LEN 256
#define MAX_COMPRESSED_LEN (MAX_INPUT_LEN * 2 + 1) // 最壞情況,每個字符都不同,例如 "ABC" -> "1A1B1C"// --- 函數:RLE 壓縮 ---
// 將輸入字符串進行RLE壓縮,結果存儲在output_buffer中
// 返回壓縮后的長度
int rle_compress(const char* input, char* output_buffer, int buffer_size) {if (input == NULL || output_buffer == NULL || buffer_size <= 0) {fprintf(stderr, "錯誤: 輸入或輸出緩沖區無效。\n");return -1;}int input_len = strlen(input);if (input_len == 0) {output_buffer[0] = '\0';return 0;}int output_idx = 0;int i = 0;while (i < input_len) {char current_char = input[i];int count = 0;// 統計連續重復的字符while (i < input_len && input[i] == current_char) {count++;i++;}// 將計數和字符寫入輸出緩沖區// 確保不會超出緩沖區大小if (output_idx + snprintf(output_buffer + output_idx, buffer_size - output_idx, "%d%c", count, current_char) >= buffer_size) {fprintf(stderr, "錯誤: 壓縮輸出緩沖區溢出。\n");output_buffer[output_idx] = '\0'; // 確保字符串終止return -1; // 壓縮失敗}output_idx += snprintf(output_buffer + output_idx, buffer_size - output_idx, "%d%c", count, current_char);}output_buffer[output_idx] = '\0'; // 確保字符串終止return output_idx;
}// --- 函數:RLE 解壓 ---
// 將RLE壓縮后的字符串解壓,結果存儲在output_buffer中
// 返回解壓后的長度
int rle_decompress(const char* input, char* output_buffer, int buffer_size) {if (input == NULL || output_buffer == NULL || buffer_size <= 0) {fprintf(stderr, "錯誤: 輸入或輸出緩沖區無效。\n");return -1;}int input_len = strlen(input);if (input_len == 0) {output_buffer[0] = '\0';return 0;}int output_idx = 0;int i = 0;while (i < input_len) {int count = 0;// 讀取數字 (重復次數)while (i < input_len && input[i] >= '0' && input[i] <= '9') {count = count * 10 + (input[i] - '0');i++;}if (i >= input_len || count == 0) { // 格式錯誤或計數為0fprintf(stderr, "錯誤: 解壓輸入格式錯誤。\n");output_buffer[output_idx] = '\0';return -1;}char current_char = input[i]; // 讀取字符i++;// 將字符重復寫入輸出緩沖區for (int k = 0; k < count; k++) {if (output_idx >= buffer_size - 1) { // 確保不會超出緩沖區大小fprintf(stderr, "錯誤: 解壓輸出緩沖區溢出。\n");output_buffer[output_idx] = '\0';return -1; // 解壓失敗}output_buffer[output_idx++] = current_char;}}output_buffer[output_idx] = '\0'; // 確保字符串終止return output_idx;
}int main() {printf("====== C語言模擬簡易RLE壓縮與解壓 ======\n");char input_string[MAX_INPUT_LEN];char compressed_string[MAX_COMPRESSED_LEN];char decompressed_string[MAX_INPUT_LEN];// 示例1: 包含重復字符的字符串strcpy(input_string, "AAABBBCCCDDEFFF");printf("\n原始字符串: \"%s\"\n", input_string);int compressed_len = rle_compress(input_string, compressed_string, sizeof(compressed_string));if (compressed_len != -1) {printf("壓縮后字符串: \"%s\" (長度: %d)\n", compressed_string, compressed_len);int decompressed_len = rle_decompress(compressed_string, decompressed_string, sizeof(decompressed_string));if (decompressed_len != -1) {printf("解壓后字符串: \"%s\" (長度: %d)\n", decompressed_string, decompressed_len);if (strcmp(input_string, decompressed_string) == 0) {printf("驗證: 原始與解壓字符串一致。\n");} else {printf("驗證: 原始與解壓字符串不一致!\n");}}}// 示例2: 沒有重復字符的字符串 (壓縮率可能變差)strcpy(input_string, "ABCDEF");printf("\n原始字符串: \"%s\"\n", input_string);compressed_len = rle_compress(input_string, compressed_string, sizeof(compressed_string));if (compressed_len != -1) {printf("壓縮后字符串: \"%s\" (長度: %d)\n", compressed_string, compressed_len);int decompressed_len = rle_decompress(compressed_string, decompressed_string, sizeof(decompressed_string));if (decompressed_len != -1) {printf("解壓后字符串: \"%s\" (長度: %d)\n", decompressed_string, decompressed_len);if (strcmp(input_string, decompressed_string) == 0) {printf("驗證: 原始與解壓字符串一致。\n");} else {printf("驗證: 原始與解壓字符串不一致!\n");}}}// 示例3: 單個字符重復多次strcpy(input_string, "HHHHHHHHHH");printf("\n原始字符串: \"%s\"\n", input_string);compressed_len = rle_compress(input_string, compressed_string, sizeof(compressed_string));if (compressed_len != -1) {printf("壓縮后字符串: \"%s\" (長度: %d)\n", compressed_string, compressed_len);int decompressed_len = rle_decompress(compressed_string, decompressed_string, sizeof(decompressed_string));if (decompressed_len != -1) {printf("解壓后字符串: \"%s\" (長度: %d)\n", decompressed_string, decompressed_len);if (strcmp(input_string, decompressed_string) == 0) {printf("驗證: 原始與解壓字符串一致。\n");} else {printf("驗證: 原始與解壓字符串不一致!\n");}}}printf("\n====== 模擬結束 ======\n");return 0;
}
代碼分析與邏輯透析: 這個C語言程序實現了一個簡單的RLE(Run-Length Encoding)壓縮和解壓算法,讓你從代碼層面理解壓縮的基本思想。
rle_compress
函數:輸入:
input
(待壓縮字符串),output_buffer
(存儲壓縮結果的緩沖區),buffer_size
(緩沖區大小)。核心邏輯:
使用兩個指針
i
(遍歷輸入字符串)和output_idx
(寫入輸出緩沖區)。外層
while
循環遍歷輸入字符串。內層
while
循環(while (i < input_len && input[i] == current_char)
)用于統計連續重復的字符。count
變量記錄重復次數,i
向前移動。snprintf(output_buffer + output_idx, buffer_size - output_idx, "%d%c", count, current_char)
:這是關鍵一步!它將count
(數字)和current_char
(字符)格式化為一個字符串(例如"3A"
),并寫入到output_buffer
中。snprintf
比sprintf
更安全,因為它會限制寫入的字節數,防止緩沖區溢出。output_idx
根據snprintf
的返回值(寫入的字符數)向前移動。
輸出: 返回壓縮后的字符串長度,或
-1
表示失敗。
rle_decompress
函數:輸入:
input
(RLE壓縮后的字符串),output_buffer
(存儲解壓結果的緩沖區),buffer_size
(緩沖區大小)。核心邏輯:
同樣使用兩個指針
i
(遍歷輸入字符串)和output_idx
(寫入輸出緩沖區)。外層
while
循環遍歷壓縮后的字符串。內層
while
循環(while (i < input_len && input[i] >= '0' && input[i] <= '9')
)用于讀取數字,即重復次數。它將連續的數字字符轉換為整數count
。讀取緊隨數字后面的字符
current_char
。內層
for
循環:將current_char
重復count
次寫入到output_buffer
中。
輸出: 返回解壓后的字符串長度,或
-1
表示失敗。
main
函數:定義了輸入、壓縮、解壓三個緩沖區。
通過三個不同的示例字符串,演示了RLE壓縮和解壓的過程,并驗證了原始字符串與解壓后的字符串是否一致。
你可以觀察到,對于重復字符多的字符串(示例1和示例3),壓縮效果明顯;而對于沒有重復字符的字符串(示例2),壓縮后反而可能變長(因為每個字符前面都加了個
1
)。這說明了不同壓縮算法的適用場景。
通過這個模擬器,你不僅能練習C語言的字符串處理、循環、條件判斷等,還能對數據壓縮的原理有一個初步的認識。雖然RLE非常簡單,但它是很多復雜壓縮算法的基礎概念。
2.5 進程管理:Linux的“統帥之道”
在Linux中,每個運行的程序都被稱為一個進程(Process)。理解進程,是進行系統監控、性能優化和故障排除的關鍵。
2.5.1 進程的基本概念:PID、PPID、狀態
進程ID (PID): 每個進程在系統中都有一個唯一的數字ID。
父進程ID (PPID): 啟動當前進程的父進程的ID。
進程狀態: 進程在生命周期中會經歷不同的狀態:
R (Running): 正在運行或在運行隊列中等待。
S (Sleeping): 正在等待某個事件的發生(如I/O完成)。
D (Uninterruptible Sleep): 不可中斷的睡眠,通常發生在I/O操作中,無法被信號中斷。
Z (Zombie): 僵尸進程,子進程已終止,但父進程尚未回收其資源。
T (Stopped): 進程被停止(如通過Ctrl+Z)。
X (Dead): 進程已終止。
2.5.2 ps
:進程的“快照”
ps
(process status) 命令用于查看當前系統中的進程“快照”。
常用參數:
ps aux
:顯示所有用戶的進程,包括沒有控制終端的進程,以及用戶和進程的詳細信息。ps -ef
:顯示所有進程的完整格式列表。ps -l
:顯示當前終端的進程的詳細信息。
ps aux | head -n 5 # 查看前5個進程 ps -ef | grep nginx # 查找nginx進程
2.5.3 top
:實時監控進程的“儀表盤”
top
命令用于實時顯示系統中進程的動態信息,包括CPU使用率、內存使用率、進程ID、用戶等。
交互式操作:
q
:退出k
:殺死進程 (輸入PID)r
:修改進程優先級 (renice)M
:按內存使用排序P
:按CPU使用排序1
:顯示每個CPU核心的使用情況
2.5.4 kill
:終止進程的“屠龍刀”
kill
命令用于向進程發送信號,最常用的是終止進程。
信號:
SIGTERM
(15):默認信號,請求進程優雅退出(進程可以捕獲并處理)。SIGKILL
(9):強制終止進程,進程無法捕獲和忽略。SIGSTOP
(19):暫停進程。SIGCONT
(18):繼續被暫停的進程。
基本用法:
kill [信號] <PID>
kill 1234 # 向PID為1234的進程發送SIGTERM信號 kill -9 1234 # 強制殺死PID為1234的進程 kill -STOP 1234 # 暫停進程 kill -CONT 1234 # 繼續進程
2.5.5 jobs
, fg
, bg
, nohup
:后臺任務管理
jobs
: 查看當前Shell中正在運行或暫停的后臺任務。fg
(foreground): 將后臺任務切換到前臺運行。bg
(background): 將暫停的任務切換到后臺運行。nohup
(no hangup): 運行命令,即使終端關閉(掛斷)也不會停止。常用于在后臺長時間運行的程序。# 示例: ./my_long_running_app & # 在后臺運行my_long_running_app jobs # 查看后臺任務 fg %1 # 將第一個后臺任務切換到前臺 nohup ./my_server_app & # 運行服務器程序,即使關閉終端也不會停止
C語言模擬:簡易進程管理器
我們將用C語言模擬一個簡易的進程管理系統,包括進程的創建、狀態切換、列表顯示和“殺死”進程。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h> // For sleep in real Linux// --- 宏定義 ---
#define MAX_PROCESSES 10
#define MAX_PROCESS_NAME_LEN 64// --- 枚舉:進程狀態 ---
typedef enum {PROCESS_STATE_RUNNING,PROCESS_STATE_SLEEPING,PROCESS_STATE_STOPPED,PROCESS_STATE_ZOMBIE,PROCESS_STATE_DEAD
} ProcessState;// --- 結構體:模擬進程 ---
typedef struct Process {int pid;int ppid; // Parent PIDchar name[MAX_PROCESS_NAME_LEN];ProcessState state;// 實際進程還會有CPU使用率、內存、優先級等
} Process;// --- 全局變量:模擬進程列表 ---
Process simulated_processes[MAX_PROCESSES];
int next_pid = 100; // 模擬PID從100開始
int num_active_processes = 0;// --- 輔助函數:查找進程 ---
Process* find_process_by_pid(int pid) {for (int i = 0; i < num_active_processes; i++) {if (simulated_processes[i].pid == pid) {return &simulated_processes[i];}}return NULL;
}// --- 輔助函數:獲取進程狀態字符串 ---
const char* get_process_state_str(ProcessState state) {switch (state) {case PROCESS_STATE_RUNNING: return "RUNNING";case PROCESS_STATE_SLEEPING: return "SLEEPING";case PROCESS_STATE_STOPPED: return "STOPPED";case PROCESS_STATE_ZOMBIE: return "ZOMBIE";case PROCESS_STATE_DEAD: return "DEAD";default: return "UNKNOWN";}
}// --- 命令實現:創建進程 (模擬 fork/exec) ---
// 在真實Linux中,fork()創建子進程,exec()加載新程序
void cmd_create_process(const char* name, int ppid) {if (num_active_processes >= MAX_PROCESSES) {printf("create_process: 達到最大進程數。\n");return;}Process* new_proc = &simulated_processes[num_active_processes++];new_proc->pid = next_pid++;new_proc->ppid = ppid;strncpy(new_proc->name, name, MAX_PROCESS_NAME_LEN - 1);new_proc->name[MAX_PROCESS_NAME_LEN - 1] = '\0';new_proc->state = PROCESS_STATE_RUNNING; // 默認創建后是運行狀態printf("create_process: 進程 '%s' (PID: %d, PPID: %d) 已創建并運行。\n",new_proc->name, new_proc->pid, new_proc->ppid);
}// --- 命令實現:ps (模擬進程列表) ---
void cmd_ps() {printf("\n--- 模擬進程列表 (ps) ---\n");printf("PID\tPPID\tSTATE\tNAME\n");printf("----------------------------------\n");for (int i = 0; i < num_active_processes; i++) {Process* p = &simulated_processes[i];printf("%d\t%d\t%s\t%s\n", p->pid, p->ppid, get_process_state_str(p->state), p->name);}printf("----------------------------------\n");
}// --- 命令實現:kill (模擬發送信號) ---
void cmd_kill(int pid, int signal) {Process* p = find_process_by_pid(pid);if (p == NULL || p->state == PROCESS_STATE_DEAD) {printf("kill: 進程 %d 不存在或已終止。\n", pid);return;}switch (signal) {case 15: // SIGTERMprintf("kill: 向進程 %d (%s) 發送 SIGTERM 信號。\n", pid, p->name);p->state = PROCESS_STATE_ZOMBIE; // 模擬進程進入僵尸狀態,等待父進程回收printf("進程 %d (%s) 狀態變為 ZOMBIE。\n", pid, p->name);break;case 9: // SIGKILLprintf("kill: 向進程 %d (%s) 發送 SIGKILL 信號 (強制終止)。\n", pid, p->name);p->state = PROCESS_STATE_DEAD; // 強制終止,直接進入死亡狀態printf("進程 %d (%s) 狀態變為 DEAD。\n", pid, p->name);// 實際需要從列表中移除,這里簡化break;case 19: // SIGSTOPprintf("kill: 向進程 %d (%s) 發送 SIGSTOP 信號 (暫停)。\n", pid, p->name);p->state = PROCESS_STATE_STOPPED;printf("進程 %d (%s) 狀態變為 STOPPED。\n", pid, p->name);break;case 18: // SIGCONTprintf("kill: 向進程 %d (%s) 發送 SIGCONT 信號 (繼續)。\n", pid, p->name);if (p->state == PROCESS_STATE_STOPPED) {p->state = PROCESS_STATE_RUNNING;printf("進程 %d (%s) 狀態變為 RUNNING。\n", pid, p->name);} else {printf("進程 %d (%s) 未處于 STOPPED 狀態。\n", pid, p->name);}break;default:printf("kill: 不支持的信號 %d。\n", signal);break;}
}// --- 模擬主進程 (PID 1) ---
Process init_process;void init_process_manager() {init_process.pid = 1;init_process.ppid = 0; // 沒有父進程strcpy(init_process.name, "init");init_process.state = PROCESS_STATE_RUNNING;simulated_processes[0] = init_process;num_active_processes = 1;next_pid = 100; // 用戶進程從100開始
}// --- 命令解析器 (簡化版,只處理 ps, create_proc, kill) ---
void parse_and_execute_proc_command(char* command_line) {char* token;char* args[4]; // 命令 + 參數int arg_count = 0;char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n");while (token != NULL && arg_count < 4) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}if (arg_count == 0) {free(cmd_copy);return;}const char* cmd = args[0];if (strcmp(cmd, "ps") == 0) {cmd_ps();} else if (strcmp(cmd, "create_proc") == 0) {if (arg_count > 1) {// 模擬新進程的父進程是當前模擬的init進程cmd_create_process(args[1], init_process.pid);} else {printf("create_proc: 缺少進程名稱。\n");}} else if (strcmp(cmd, "kill") == 0) {if (arg_count > 1) {int pid = atoi(args[1]);int signal = 15; // 默認SIGTERMif (arg_count > 2) {if (strncmp(args[2], "-", 1) == 0) { // 檢查是否有信號參數,如 -9signal = atoi(args[2] + 1); // 跳過 '-'} else {printf("kill: 無效的信號格式。\n");free(cmd_copy); return;}}cmd_kill(pid, signal);} else {printf("kill: 缺少進程PID。\n");}} else {printf("sim_proc_mgr: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}int main() {printf("====== 簡易Linux進程管理器模擬器 ======\n");printf("可用命令: ps, create_proc <name>, kill <pid> [-signal], exit\n");init_process_manager(); // 初始化模擬的init進程char command_line[MAX_PROCESS_NAME_LEN * 2];while (true) {printf("\nsim_proc_mgr$ ");if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break;}command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_proc_command(command_line);}printf("\n====== 模擬器退出 ======\n");return 0;
}
代碼分析與邏輯透析: 這個C語言模擬器實現了一個簡易的Linux進程管理系統,讓你能從代碼層面理解進程的生命周期、狀態和基本操作。
Process
結構體:模擬了Linux進程的關鍵屬性:
pid
(進程ID)、ppid
(父進程ID)、name
(進程名稱)和state
(進程狀態,通過枚舉定義)。ProcessState
枚舉:定義了常見的進程狀態,如RUNNING
、SLEEPING
、STOPPED
、ZOMBIE
、DEAD
。
simulated_processes
全局數組:一個
Process
結構體數組,用于存儲當前系統中所有模擬的進程。next_pid
:用于分配新的進程ID。num_active_processes
:當前活躍進程的數量。
find_process_by_pid
輔助函數:根據PID在
simulated_processes
數組中查找對應的進程。
get_process_state_str
輔助函數:將
ProcessState
枚舉值轉換為可讀的字符串,方便ps
命令的輸出。
cmd_create_process
命令實現:模擬了進程的“創建”過程。在真實Linux中,這通常通過
fork()
(創建子進程)和exec()
(加載新程序)系統調用完成。這里簡化為:在
simulated_processes
數組中分配一個新位置,給它分配一個pid
,設置ppid
(這里默認是init
進程的PID),設置名稱和初始狀態為RUNNING
。
cmd_ps
命令實現:模擬了
ps
命令,遍歷simulated_processes
數組,并打印每個進程的PID、PPID、狀態和名稱。
cmd_kill
命令實現:模擬了
kill
命令,接收pid
和signal
(信號值,如9代表SIGKILL
,15代表SIGTERM
)作為參數。根據不同的信號值,改變目標進程的
state
。SIGTERM
:模擬進程進入ZOMBIE
狀態,表示它已終止但資源尚未被回收(在真實Linux中,由父進程回收)。SIGKILL
:模擬進程直接進入DEAD
狀態,表示被強制終止。SIGSTOP
:模擬進程進入STOPPED
狀態。SIGCONT
:模擬進程從STOPPED
狀態恢復到RUNNING
。
init_process_manager
函數:在
main
函數開始時調用,用于初始化一個模擬的“init”進程(PID為1),它是所有其他進程的祖先。
parse_and_execute_proc_command
函數:解析用戶輸入的命令(
ps
,create_proc
,kill
)及其參數。對于
kill
命令,它會解析可選的信號參數(例如-9
)。
main
函數:初始化進程管理器。
進入一個無限循環,模擬Shell的交互式命令行界面,等待用戶輸入進程管理命令。
通過這個模擬器,你可以:
創建進程: 嘗試
create_proc my_app
來“啟動”一個新進程。查看進程: 運行
ps
來查看當前所有模擬進程的狀態。殺死進程: 嘗試
kill <PID>
(默認SIGTERM)或kill <PID> -9
(強制SIGKILL),觀察進程狀態的變化。暫停/繼續進程: 嘗試
kill <PID> -19
和kill <PID> -18
。理解進程狀態: 觀察進程在不同操作下狀態的轉換。
2.6 用戶與組管理:Linux的“治理之道”
在多用戶操作系統Linux中,用戶和組是權限管理和資源分配的基礎。
2.6.1 用戶:身份的標識
每個用戶都有一個唯一的用戶名(Username)和一個唯一的用戶ID(UID)。
用戶登錄系統后,系統會根據其UID來識別其身份和權限。
root
用戶: UID為0,是Linux系統的超級管理員,擁有最高權限。
2.6.2 組:權限的集合
每個組都有一個唯一的組名(Group Name)和一個唯一的組ID(GID)。
組是用戶的集合。將用戶添加到組中,可以方便地管理一組用戶對文件或目錄的權限。
每個用戶至少屬于一個主組(Primary Group),也可以屬于多個附加組(Supplementary Groups)。
2.6.3 用戶管理命令:useradd
, userdel
, usermod
, passwd
useradd
:創建新用戶sudo useradd new_user # 創建一個新用戶new_user sudo useradd -m new_user # 創建用戶并同時創建其主目錄 sudo useradd -g developers new_user # 創建用戶并指定其主組為developers sudo useradd -G sudo,users new_user # 創建用戶并將其添加到sudo和users附加組
userdel
:刪除用戶sudo userdel old_user # 刪除用戶old_user,但保留其主目錄和文件 sudo userdel -r old_user # 刪除用戶old_user,并同時刪除其主目錄和文件
usermod
:修改用戶信息sudo usermod -l new_name old_name # 將用戶old_name重命名為new_name sudo usermod -d /home/new_home_dir new_user # 修改用戶主目錄 sudo usermod -g new_group user1 # 修改用戶主組 sudo usermod -aG sudo user1 # 將user1添加到sudo附加組 (-a表示append)
passwd
:設置用戶密碼passwd # 修改當前用戶密碼 sudo passwd user1 # 修改user1的密碼
2.6.4 組管理命令:groupadd
, groupdel
, groupmod
groupadd
:創建新組sudo groupadd new_group # 創建一個新組new_group
groupdel
:刪除組sudo groupdel old_group # 刪除組old_group
groupmod
:修改組信息sudo groupmod -n new_group_name old_group_name # 將組old_group_name重命名為new_group_name
C語言模擬:簡易用戶與組管理系統
我們將用C語言模擬一個簡易的用戶與組管理系統,包括用戶和組的添加、刪除、修改,以及用戶與組的關聯。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>// --- 宏定義 ---
#define MAX_USERS 10
#define MAX_GROUPS 5
#define MAX_MEMBERS_PER_GROUP 5
#define MAX_USERNAME_LEN 32
#define MAX_GROUPNAME_LEN 32// --- 結構體:模擬用戶 ---
typedef struct User {int uid; // User IDchar username[MAX_USERNAME_LEN];char password[MAX_USERNAME_LEN]; // 簡化:直接存儲密碼字符串int primary_gid; // 主組ID// 實際用戶還有更多屬性,如主目錄、shell等
} User;// --- 結構體:模擬組 ---
typedef struct Group {int gid; // Group IDchar groupname[MAX_GROUPNAME_LEN];char members[MAX_MEMBERS_PER_GROUP][MAX_USERNAME_LEN]; // 組內成員用戶名列表int num_members;
} Group;// --- 全局變量:模擬用戶和組列表 ---
User simulated_users[MAX_USERS];
int num_users = 0;
int next_uid = 1000; // 用戶ID從1000開始Group simulated_groups[MAX_GROUPS];
int num_groups = 0;
int next_gid = 1000; // 組ID從1000開始// --- 輔助函數:查找用戶 ---
User* find_user_by_name(const char* username) {for (int i = 0; i < num_users; i++) {if (strcmp(simulated_users[i].username, username) == 0) {return &simulated_users[i];}}return NULL;
}// --- 輔助函數:查找組 ---
Group* find_group_by_name(const char* groupname) {for (int i = 0; i < num_groups; i++) {if (strcmp(simulated_groups[i].groupname, groupname) == 0) {return &simulated_groups[i];}}return NULL;
}Group* find_group_by_gid(int gid) {for (int i = 0; i < num_groups; i++) {if (simulated_groups[i].gid == gid) {return &simulated_groups[i];}}return NULL;
}// --- 輔助函數:將用戶添加到組 ---
bool add_user_to_group_internal(Group* group_ptr, const char* username) {if (group_ptr == NULL) return false;if (group_ptr->num_members >= MAX_MEMBERS_PER_GROUP) {printf("組 '%s' 成員已滿。\n", group_ptr->groupname);return false;}// 檢查是否已在組中for (int i = 0; i < group_ptr->num_members; i++) {if (strcmp(group_ptr->members[i], username) == 0) {printf("用戶 '%s' 已在組 '%s' 中。\n", username, group_ptr->groupname);return true; // 視為成功}}strncpy(group_ptr->members[group_ptr->num_members], username, MAX_USERNAME_LEN - 1);group_ptr->members[group_ptr->num_members][MAX_USERNAME_LEN - 1] = '\0';group_ptr->num_members++;return true;
}// --- 輔助函數:將用戶從組中移除 ---
bool remove_user_from_group_internal(Group* group_ptr, const char* username) {if (group_ptr == NULL) return false;for (int i = 0; i < group_ptr->num_members; i++) {if (strcmp(group_ptr->members[i], username) == 0) {// 移動最后一個成員到當前位置group_ptr->members[i][0] = '\0'; // 清空group_ptr->members[i] = group_ptr->members[group_ptr->num_members - 1]; // 修正:直接復制字符串strncpy(group_ptr->members[i], group_ptr->members[group_ptr->num_members - 1], MAX_USERNAME_LEN - 1);group_ptr->members[group_ptr->num_members - 1][0] = '\0'; // 清空最后一個group_ptr->num_members--;return true;}}return false;
}// --- 命令實現:groupadd ---
void cmd_groupadd(const char* groupname) {if (find_group_by_name(groupname) != NULL) {printf("groupadd: 組 '%s' 已存在。\n", groupname);return;}if (num_groups >= MAX_GROUPS) {printf("groupadd: 達到最大組數量。\n");return;}Group* new_group = &simulated_groups[num_groups++];new_group->gid = next_gid++;strncpy(new_group->groupname, groupname, MAX_GROUPNAME_LEN - 1);new_group->groupname[MAX_GROUPNAME_LEN - 1] = '\0';new_group->num_members = 0;printf("groupadd: 組 '%s' (GID: %d) 創建成功。\n", groupname, new_group->gid);
}// --- 命令實現:useradd ---
void cmd_useradd(const char* username, const char* primary_group_name) {if (find_user_by_name(username) != NULL) {printf("useradd: 用戶 '%s' 已存在。\n", username);return;}if (num_users >= MAX_USERS) {printf("useradd: 達到最大用戶數量。\n");return;}int primary_gid = -1;if (primary_group_name != NULL) {Group* pg = find_group_by_name(primary_group_name);if (pg != NULL) {primary_gid = pg->gid;} else {printf("useradd: 主組 '%s' 不存在,將使用默認組。\n", primary_group_name);// 真實Linux會嘗試創建同名組或報錯}}// 簡化:如果沒有指定主組或主組不存在,則創建一個同名主組if (primary_gid == -1) {cmd_groupadd(username); // 嘗試創建同名組作為主組Group* pg = find_group_by_name(username);if (pg != NULL) {primary_gid = pg->gid;} else {printf("useradd: 無法創建默認主組 '%s'。\n", username);return;}}User* new_user = &simulated_users[num_users++];new_user->uid = next_uid++;strncpy(new_user->username, username, MAX_USERNAME_LEN - 1);new_user->username[MAX_USERNAME_LEN - 1] = '\0';strcpy(new_user->password, "123456"); // 默認密碼new_user->primary_gid = primary_gid;// 將用戶添加到其主組Group* pg = find_group_by_gid(primary_gid);if (pg != NULL) {add_user_to_group_internal(pg, username);}printf("useradd: 用戶 '%s' (UID: %d, GID: %d) 創建成功。\n",username, new_user->uid, new_user->primary_gid);
}// --- 命令實現:userdel ---
void cmd_userdel(const char* username) {User* u = find_user_by_name(username);if (u == NULL) {printf("userdel: 用戶 '%s' 不存在。\n", username);return;}// 從所有組中移除該用戶for (int i = 0; i < num_groups; i++) {remove_user_from_group_internal(&simulated_groups[i], username);}// 從用戶列表中移除for (int i = 0; i < num_users; i++) {if (strcmp(simulated_users[i].username, username) == 0) {// 移動最后一個用戶到當前位置simulated_users[i] = simulated_users[num_users - 1];num_users--;printf("userdel: 用戶 '%s' 已刪除。\n", username);return;}}
}// --- 命令實現:groupdel ---
void cmd_groupdel(const char* groupname) {Group* g = find_group_by_name(groupname);if (g == NULL) {printf("groupdel: 組 '%s' 不存在。\n", groupname);return;}// 檢查是否有用戶的主組是該組for (int i = 0; i < num_users; i++) {if (simulated_users[i].primary_gid == g->gid) {printf("groupdel: 組 '%s' 仍有用戶以此為主組,無法刪除。\n", groupname);return;}}// 從組列表中移除for (int i = 0; i < num_groups; i++) {if (strcmp(simulated_groups[i].groupname, groupname) == 0) {simulated_groups[i] = simulated_groups[num_groups - 1];num_groups--;printf("groupdel: 組 '%s' 已刪除。\n", groupname);return;}}
}// --- 命令實現:usermod -aG (添加用戶到附加組) ---
void cmd_usermod_add_group(const char* username, const char* groupname) {User* u = find_user_by_name(username);if (u == NULL) {printf("usermod: 用戶 '%s' 不存在。\n", username);return;}Group* g = find_group_by_name(groupname);if (g == NULL) {printf("usermod: 組 '%s' 不存在。\n", groupname);return;}if (add_user_to_group_internal(g, username)) {printf("usermod: 用戶 '%s' 已添加到組 '%s'。\n", username, groupname);}
}// --- 命令實現:passwd (簡化) ---
void cmd_passwd(const char* username, const char* new_password) {User* u = find_user_by_name(username);if (u == NULL) {printf("passwd: 用戶 '%s' 不存在。\n", username);return;}strncpy(u->password, new_password, MAX_USERNAME_LEN - 1);u->password[MAX_USERNAME_LEN - 1] = '\0';printf("passwd: 用戶 '%s' 的密碼已修改。\n", username);
}// --- 命令實現:id ---
void cmd_id(const char* username) {User* u = find_user_by_name(username);if (u == NULL) {printf("id: 用戶 '%s' 不存在。\n", username);return;}printf("uid=%d(%s) gid=%d(%s) groups=", u->uid, u->username, u->primary_gid, find_group_by_gid(u->primary_gid)->groupname);// 查找所有包含該用戶的組 (附加組)bool first_group = true;for (int i = 0; i < num_groups; i++) {for (int j = 0; j < simulated_groups[i].num_members; j++) {if (strcmp(simulated_groups[i].members[j], username) == 0) {if (!first_group) printf(",");printf("%d(%s)", simulated_groups[i].gid, simulated_groups[i].groupname);first_group = false;}}}printf("\n");
}// --- 命令實現:whoami ---
void cmd_whoami() {printf("%s\n", current_user); // 模擬當前登錄用戶
}// --- 命令實現:users ---
void cmd_users() {printf("--- 當前所有用戶 ---\n");for (int i = 0; i < num_users; i++) {printf("%s ", simulated_users[i].username);}printf("\n--------------------\n");
}// --- 命令實現:groups ---
void cmd_groups(const char* username) {User* u = find_user_by_name(username);if (u == NULL) {printf("groups: 用戶 '%s' 不存在。\n", username);return;}printf("用戶 '%s' 所屬的組: ", username);printf("%s", find_group_by_gid(u->primary_gid)->groupname); // 主組// 附加組for (int i = 0; i < num_groups; i++) {if (simulated_groups[i].gid == u->primary_gid) continue; // 跳過主組for (int j = 0; j < simulated_groups[i].num_members; j++) {if (strcmp(simulated_groups[i].members[j], username) == 0) {printf(" %s", simulated_groups[i].groupname);}}}printf("\n");
}// --- 命令解析器 (簡化版,只處理用戶和組管理命令) ---
void parse_and_execute_user_command(char* command_line) {char* token;char* args[5]; // 命令 + 參數int arg_count = 0;char* cmd_copy = strdup(command_line);if (cmd_copy == NULL) {fprintf(stderr, "內存分配失敗!\n");return;}token = strtok(cmd_copy, " \t\n");while (token != NULL && arg_count < 5) {args[arg_count++] = token;token = strtok(NULL, " \t\n");}if (arg_count == 0) {free(cmd_copy);return;}const char* cmd = args[0];if (strcmp(cmd, "groupadd") == 0) {if (arg_count > 1) {cmd_groupadd(args[1]);} else {printf("groupadd: 缺少組名稱。\n");}} else if (strcmp(cmd, "useradd") == 0) {if (arg_count > 1) {char* username = args[1];char* primary_group = NULL;// 簡化:查找 -g 參數for (int i = 2; i < arg_count; i++) {if (strcmp(args[i], "-g") == 0) {if (i + 1 < arg_count) {primary_group = args[i+1];break;}}}cmd_useradd(username, primary_group);} else {printf("useradd: 缺少用戶名。\n");}} else if (strcmp(cmd, "userdel") == 0) {if (arg_count > 1) {cmd_userdel(args[1]);} else {printf("userdel: 缺少用戶名。\n");}} else if (strcmp(cmd, "groupdel") == 0) {if (arg_count > 1) {cmd_groupdel(args[1]);} else {printf("groupdel: 缺少組名稱。\n");}} else if (strcmp(cmd, "usermod") == 0) {if (arg_count > 3 && strcmp(args[1], "-aG") == 0) { // usermod -aG group usercmd_usermod_add_group(args[3], args[2]);} else {printf("usermod: 暫不支持此參數組合或缺少操作數。\n");}} else if (strcmp(cmd, "passwd") == 0) {if (arg_count > 2) {cmd_passwd(args[1], args[2]);} else {printf("passwd: 缺少用戶名和新密碼。\n");}} else if (strcmp(cmd, "id") == 0) {if (arg_count > 1) {cmd_id(args[1]);} else {cmd_id(current_user); // 默認顯示當前用戶}} else if (strcmp(cmd, "whoami") == 0) {cmd_whoami();} else if (strcmp(cmd, "users") == 0) {cmd_users();} else if (strcmp(cmd, "groups") == 0) {if (arg_count > 1) {cmd_groups(args[1]);} else {cmd_groups(current_user); // 默認顯示當前用戶所屬組}}else {printf("sim_user_mgr: 命令 '%s' 未找到。\n", cmd);}free(cmd_copy);
}int main() {printf("====== 簡易Linux用戶與組管理器模擬器 ======\n");printf("可用命令: groupadd <name>, useradd <name> [-g <group>], userdel <name>, groupdel <name>, usermod -aG <group> <user>, passwd <user> <new_pass>, id [user], whoami, users, groups [user], exit\n");// 初始化一些默認用戶和組cmd_groupadd("root"); // GID 1000cmd_groupadd("users"); // GID 1001cmd_groupadd("developers"); // GID 1002cmd_groupadd("sudo"); // GID 1003cmd_useradd("root", "root"); // UID 1000, GID 1000cmd_useradd("sim_user", "users"); // UID 1001, GID 1001cmd_useradd("dev_user", "developers"); // UID 1002, GID 1002// 模擬將 sim_user 添加到 sudo 組cmd_usermod_add_group("sim_user", "sudo");char command_line[MAX_USERNAME_LEN * 4];while (true) {printf("\nsim_user_mgr$ ");if (fgets(command_line, sizeof(command_line), stdin) == NULL) {break;}command_line[strcspn(command_line, "\n")] = 0;if (strcmp(command_line, "exit") == 0) {break;}parse_and_execute_user_command(command_line);}printf("\n====== 模擬器退出 ======\n");return 0;
}
代碼分析與邏輯透析: 這個C語言模擬器實現了一個簡易的Linux用戶與組管理系統,讓你能從代碼層面理解用戶、組、以及它們之間關系的管理。
User
結構體:模擬了Linux用戶的基本屬性:
uid
(用戶ID)、username
(用戶名)、password
(密碼,這里簡化為明文存儲)和primary_gid
(主組ID)。
Group
結構體:模擬了Linux組的基本屬性:
gid
(組ID)、groupname
(組名)和members
(組內成員的用戶名列表)。members
是一個二維字符數組,存儲屬于該組的所有用戶的用戶名。
全局變量 (
simulated_users
,simulated_groups
,next_uid
,next_gid
):用于存儲系統中所有模擬的用戶和組信息。
next_uid
和next_gid
用于分配新的唯一ID。
輔助函數 (
find_user_by_name
,find_group_by_name
,find_group_by_gid
,add_user_to_group_internal
,remove_user_from_group_internal
):這些函數是用戶和組管理操作的基礎。
add_user_to_group_internal
和remove_user_from_group_internal
是內部函數,用于在組的成員列表中添加或移除用戶,被useradd
、usermod
等命令調用。
用戶管理命令實現 (
cmd_useradd
,cmd_userdel
,cmd_usermod_add_group
,cmd_passwd
):cmd_useradd
:創建新的
User
結構體,分配UID和用戶名。關鍵邏輯: 如果用戶沒有指定主組,或者指定的主組不存在,它會嘗試創建一個與用戶名同名的組作為其主組(模擬真實Linux的默認行為)。
最后,將新用戶添加到其主組的成員列表中。
cmd_userdel
:刪除用戶時,首先將其從所有組(包括主組和附加組)的成員列表中移除。
然后從
simulated_users
數組中移除該用戶。
cmd_usermod_add_group
:模擬
usermod -aG <group> <user>
,將一個用戶添加到指定的附加組中。它會調用
add_user_to_group_internal
來完成實際的添加操作。
cmd_passwd
: 簡化為直接修改用戶結構體中的password
字段。
組管理命令實現 (
cmd_groupadd
,cmd_groupdel
):cmd_groupadd
: 創建新的Group
結構體,分配GID和組名。cmd_groupdel
:刪除組時,會檢查是否有任何用戶仍將該組設置為主組。如果有,則阻止刪除,因為在真實Linux中,用戶必須有一個主組。
如果沒有用戶以其為主組,則從
simulated_groups
數組中移除該組。
信息查詢命令實現 (
cmd_id
,cmd_whoami
,cmd_users
,cmd_groups
):cmd_id
: 模擬id
命令,顯示用戶的UID、主GID以及所屬的所有組(通過遍歷所有組的成員列表來查找附加組)。cmd_whoami
: 簡單打印當前模擬的用戶名。cmd_users
: 打印所有模擬用戶的用戶名。cmd_groups
: 打印指定用戶所屬的所有組(包括主組和附加組)。
parse_and_execute_user_command
函數:解析用戶輸入的命令和參數,并調用對應的用戶/組管理函數。
對
usermod -aG
這種多參數命令進行了簡化解析。
main
函數:初始化時,預設了一些默認的用戶和組,例如
root
、users
、developers
、sudo
。模擬了將
sim_user
添加到sudo
組的操作。進入一個無限循環,模擬Shell的交互式命令行界面,等待用戶輸入用戶/組管理命令。
通過這個模擬器,你可以:
創建/刪除用戶和組: 嘗試
useradd test_user
,groupadd admins
,然后userdel test_user
,groupdel admins
。修改用戶/組: 嘗試
usermod -aG sudo sim_user
,然后id sim_user
查看變化。查詢信息: 嘗試
id root
,whoami
,users
,groups sim_user
。理解用戶/組關系: 觀察用戶如何屬于主組和附加組,以及刪除組時對用戶的影響。
2.7 小結與展望
恭喜你,老鐵!你已經成功闖過了“Linux與C高級編程”學習之路的第二關:Linux Shell命令進階!
在這一部分中,我們:
深入理解了文件權限的精髓,掌握了
chmod
、chown
、chgrp
等命令,并通過C語言模擬器,讓你從位操作層面理解權限的控制。學會了**輸入輸出重定向(
>
,>>
,<
)和管道(|
)**的“乾坤大挪移”之術,并通過C語言模擬了cat | grep
的流程,讓你理解命令間高效協作的本質。掌握了**文件搜索(
find
,grep
)和文件處理(diff
,sort
,uniq
,wc
)**的“火眼金睛”,并通過一個龐大的C語言模擬器,讓你親手實現find
和grep
的組合搜索。學習了**壓縮與解壓(
tar
,gzip
,bzip2
,xz
,zip
)**的“縮骨功”,并通過C語言模擬了簡單的RLE算法,讓你理解壓縮的基本原理。深入了解了**進程管理(
ps
,top
,kill
,jobs
,fg
,bg
,nohup
)**的“統帥之道”,并通過C語言模擬器,讓你親手創建、管理和“殺死”進程。掌握了**用戶與組管理(
useradd
,userdel
,usermod
,passwd
,groupadd
,groupdel
,groupmod
)**的“治理之道”,并通過C語言模擬器,讓你理解用戶和組的創建、關聯和刪除邏輯。
這些命令和它們背后的原理,是你在Linux環境下進行任何開發(尤其是嵌入式開發)的必備技能。它們能讓你高效地管理文件、監控系統、處理數據,并與系統進行深度交互。
接下來,我們將進入更具挑戰性的第三部分:Linux Shell腳本編程(概念、變量、語句,分支語句、循環語句,函數)!我們將把這些零散的Shell命令串聯起來,編寫自動化腳本,讓Linux系統按照你的意愿自動完成任務!
請記住,學習Linux,多敲命令,多實踐是王道!不要害怕命令行,它會成為你最忠實、最強大的伙伴!
敬請期待我的下一次更新!如果你在學習過程中有任何疑問,或者對代碼有任何改進的想法,隨時在評論區告訴我,咱們一起交流,一起成為Linux與C編程的“大神”!