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

C 語言從零手寫 Linux Shell:內建指令、外部程式執行、管道與重導向雛形

     編輯:關於C語言

設計理念

Linux 指令分內建 (Shell 進程直接執行,如 `cd`, `exit`, `export`) 與外部 (獨立可執行檔,如 `ls`, `grep`, `cat`)。Shell 核心迴圈:**讀取 → 解析 → 派生子進程執行外部指令 / 直接執行內建指令 → 等待/繼續**。

完整極簡 Shell 實作 (約 100 行)


#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_LINE 1024
#define MAX_ARGS 64

void print_prompt(void) {
    char cwd[MAX_LINE], host[MAX_LINE];
    const char *user = getenv("USER");
    gethostname(host, sizeof(host));
    getcwd(cwd, sizeof(cwd));
    printf("%s@%s:%s%s ", user, host, cwd, (geteuid()==0) ? "#" : "$");
}

int parse_line(char *line, char **argv) {
    int argc = 0;
    char *token = strtok(line, " \t\n");
    while (token && argc < MAX_ARGS-1) {
        argv[argc++] = token;
        token = strtok(NULL, " \t\n");
    }
    argv[argc] = NULL;
    return argc;
}

bool is_builtin(const char *cmd) {
    return strcmp(cmd, "cd") == 0 || strcmp(cmd, "exit") == 0;
}

int exec_builtin(char **argv) {
    if (strcmp(argv[0], "cd") == 0) {
        const char *dir = argv[1] ? argv[1] : getenv("HOME");
        if (chdir(dir) != 0) perror("cd");
        return 1;  // 內建指令已處理
    }
    if (strcmp(argv[0], "exit") == 0) exit(0);
    return 0;  // 非內建
}

int main(void) {
    char line[MAX_LINE];
    char *argv[MAX_ARGS];

    while (1) {
        print_prompt();
        if (!fgets(line, MAX_LINE, stdin)) break;  // EOF (Ctrl+D)

        int argc = parse_line(line, argv);
        if (argc == 0) continue;  // 空行

        if (exec_builtin(argv)) continue;  // 內建指令直接處理

        /* 外部指令:fork + execvp */
        pid_t pid = fork();
        if (pid < 0) { perror("fork"); continue; }

        if (pid == 0) {  // 子進程
            execvp(argv[0], argv);
            perror(argv[0]);  // execvp 失敗才會執行到這
            _exit(1);
        } else {  // 父進程
            int status;
            waitpid(pid, &status, 0);
        }
    }
    return 0;
}

核心組件解析

1. 提示字元顯示

  • `getenv("USER")`、`gethostname()`、`getcwd()` 組成標準提示字元。
  • Root 顯示 `#`,一般使用者顯示 `$`。

2. 指令行解析

  • `strtok` 以空白/Tab/換行分割,支援多參數。
  • 引號/跳脫字元處理**未實作**,屬於進階解析器領域 (可用 `getline` + 狀態機)。

3. 內建指令處理

  • `cd` 必須在 Shell 進程執行 (改變當前工作目錄),`fork` 子進程 `chdir` 無效。
  • `exit` 直接結束 Shell。

4. 外部指令執行

sequenceDiagram
    Shell->>fork: 建立子進程
    Child->>execvp: 替換映像為指令程式
    Parent->>waitpid: 等待子進程結束
    Child-->>Kernel: 退出碼
    Parent->>Shell: 繼續迴圈
  • `execvp` 在 `PATH` 中尋找可執行檔,參數陣列以 `NULL` 結尾。
  • 子進程 `execvp` 成功則**不再返回**;失敗印錯誤並 `_exit`。

進階擴展方向

1. 管道


/* ls | grep c */
int pipefd[2];
pipe(pipefd);
if (fork() == 0) {  // 左側指令
    dup2(pipefd[1], STDOUT_FILENO);
    close(pipefd[0]); close(pipefd[1]);
    execvp("ls", (char*[]){"ls", NULL});
}
if (fork() == 0) {  // 右側指令
    dup2(pipefd[0], STDIN_FILENO);
    close(pipefd[0]); close(pipefd[1]);
    execvp("grep", (char*[]){"grep", "c", NULL});
}
close(pipefd[0]); close(pipefd[1]);
wait(NULL); wait(NULL);

2. 輸入/輸出重導向


/* cmd > out.txt */
int fd = open("out.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
close(fd);
execvp(...);

3. 背景執行 & 作業控制

  • `cmd &`:父進程不 `wait`,註冊 `SIGCHLD` 處理器回收殭屍進程。
  • `jobs`、`fg`、`bg`、`Ctrl+Z` (SIGTSTP) 需完整終端機控制 (tcsetpgrp)。

4. 環境變數與別名


/* export VAR=value */
setenv("VAR", "value", 1);
/* alias ll='ls -l' */
typedef struct { char *name; char *value; } Alias;

編譯與測試


$ gcc -std=c99 -Wall -Wextra -o mysh mysh.c
$ ./mysh
user@host:/home/user$ ls -l
total 12
-rwxr-xr-x 1 user user 8432 Jan 1 12:00 mysh
-rw-r--r-- 1 user user  215 Jan 1 12:00 mysh.c
user@host:/home/user$ cd ..
user@host:/home$ pwd
/home
user@host:/home$ exit
$

常見坑點與修正

現象原因修正
`cd` 無效在子進程 `chdir`父進程直接呼叫 `chdir`
管道掛住未關閉未使用的管道端點每進程 `close` 所有不用的 `pipefd`
殭屍進程累積背景任務未 `wait`安裝 `SIGCHLD` handler 呼叫 `waitpid(-1, NULL, WNOHANG)`
指令未找到`PATH` 未正確傳遞`execvp` 自動用環境 `PATH`,確認 `environ` 完整

結語

一個極簡 Shell 只需 **`fork` + `execvp` + `waitpid` + 內建指令分派**。在這 100 行骨架上,逐步加入管道、重導向、作業控制、別名、歷史、補全,即可演化成功能完整的 Shell。理解這個核心迴圈,是掌握 Unix 進程模型與 Shell 內部機制的最佳切入點。

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