跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

用 C 语言从零实现 Linux Shell:原理与实践

基于 C 语言系统调用构建简易 Linux Shell,涵盖进程创建、命令执行及内置命令处理。通过 fork、execvp 实现外部命令运行,利用 chdir 解决 cd 指令作用域问题,并演示环境变量展开逻辑。适合深入理解操作系统进程模型与文件描述符机制的学习者参考。

编程诗人发布于 2025/11/6更新于 2026/8/1737 浏览
用 C 语言从零实现 Linux Shell:原理与实践

简易版:Shell 基础构建

一个标准的 Shell 启动后,通常会先显示提示符,等待用户输入。为了模拟这一行为,我们需要获取当前环境变量信息,比如用户名和家目录。这里直接调用 getenv() 函数即可,头文件包含 <stdlib.h>。

命令行输入与解析

Shell 的核心在于处理用户输入的字符串。我们使用 fgets() 从标准输入流读取整行命令,例如 ls、pwd 或 touch。读取后记得去除末尾的换行符,否则会影响后续处理。

#define MAX 32
char str[MAX];
fgets(str, sizeof(str) - 1, stdin);
str[strnlen(str, MAX) - 1] = '\0'; // 安全地去除换行符

接下来需要将这串字符按空格分割成独立的参数数组。C 标准库中的 strtok() 是处理此类任务的高效工具。需要注意的是,strtok 会修改原字符串(将分隔符替换为 \0),且状态管理特殊:首次调用传入待分割字符串,后续迭代只需传入 NULL,否则会重置分割位置。

#include <string.h>
char* argv[MAX] = { NULL };
const char* delim = " \t\n"; // 常见的空白符作为分隔符
int i = 0;
argv[i++] = strtok(str, delim);
while (argv[i - 1]) {
    argv[i++] = strtok(NULL, delim);
}

进程创建与执行

拿到参数数组后,就可以利用系统调用来执行命令了。核心流程是 fork() 创建子进程,然后在子进程中用 execvp() 替换当前映像。

pid_t d = fork();
if (d == 0) {
    // 子进程逻辑
    if (argv[0] == NULL) return 0;
    int count = execvp(argv[], argv);
     (count < ) perror();
    (EXIT_FAILURE);
}  {
    
    waitpid(, , ); 
}
0
if
0
"execvp failed"
exit
else
// 父进程逻辑
-1
NULL
0
// 阻塞等待子进程结束

这里采用阻塞等待 (waitpid) 确保父进程不会在子进程退出前继续循环,保持交互体验的一致性。当然,实际项目中也可以根据需求改为非阻塞模式。

代码封装

为了让结构更清晰,我们将上述逻辑封装成独立函数。注意变量作用域的管理,特别是全局变量 delim 和 argv 的初始化。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define LEFT "["
#define RIGHT "]"
#define MAX 32

const char* delim = " \t\n";
char str[MAX];
char* argv[MAX] = { NULL };

// 打印提示符
void command_printf() {
    printf(LEFT "%s"@"%s" "#" RIGHT " ", getenv("USER"), getenv("HOME"));
}

// 获取输入
char* command_get(char str[MAX], int size) {
    char* pc = fgets(str, size, stdin);
    if (pc) {
        size_t len = strlen(str);
        if (len > 0 && str[len - 1] == '\n') str[len - 1] = '\0';
    }
    return pc;
}

// 解析参数
void command_extraction(char str[MAX], const char* delim, char* argv[MAX]) {
    int i = 0;
    argv[i++] = strtok(str, delim);
    while (argv[i - 1]) {
        argv[i++] = strtok(NULL, delim);
    }
}

// 执行命令
void command_use(char* argv[MAX]) {
    pid_t d = fork();
    if (d == 0) {
        if (argv[0] == NULL) return;
        int count = execvp(argv[0], argv);
        if (count < 0) perror("execvp failed");
        exit(EXIT_FAILURE);
    } else {
        waitpid(-1, NULL, 0);
    }
}

int main() {
    while (1) {
        command_printf();
        char* pc = command_get(str, sizeof(str));
        if (!pc) break;
        command_extraction(str, delim, argv);
        command_use(argv);
    }
    return 0;
}

运行效果如下所示,Shell 能够正常回显并执行外部命令: 文章配图


挑战版:内置命令处理

完成基础版本后,你会发现像 cd 或 echo $PATH 这样的命令无法正常工作。这是因为 Shell 命令分为两类:

  1. 外部命令:如 ls、cat,是磁盘上的可执行文件,通过 fork + exec 执行。
  2. 内置命令:如 cd、exit、export,直接影响 Shell 自身环境,必须在父进程中直接处理,不能 fork 子进程。

解决 cd 指令

cd 改变的是当前工作目录。如果在子进程中执行 chdir,父进程的目录并不会改变,导致 Shell 重启后回到原点。因此,cd 必须在父进程中直接调用 chdir()。

if (strcmp(argv[0], "cd") == 0 && argc == 2) {
    if (strcmp(argv[1], "./") == 0) return; // 不做任何操作
    const char* path = argv[1];
    if (chdir(path) == -1) {
        printf("路径执行错误\n");
        return;
    }
}

解决 echo 命令

对于 echo $PATH,Shell 需要先进行变量展开,将 $PATH 替换为实际的值(如 /usr/local/sbin:...),然后再传递给程序或直接打印。这里我们在执行前判断参数是否以 $ 开头,若是则调用 getenv() 获取值并替换。

if (strcmp(argv[0], "echo") == 0 && argc == 2) {
    char* pc = argv[1];
    if (pc[0] == '$' && strlen(pc) > 1) {
        char* var_name = pc + 1;
        char* value = getenv(var_name);
        if (value) argv[1] = value;
    }
}

这样处理后,echo 就能正确输出环境变量内容了。其他如 export 等指令也可依此逻辑扩展,核心在于正确处理环境变量与作用域问题。

目录

  1. 简易版:Shell 基础构建
  2. 命令行输入与解析
  3. 进程创建与执行
  4. 代码封装
  5. 挑战版:内置命令处理
  6. 解决 cd 指令
  7. 解决 echo 命令
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python Django 在线音乐播放平台项目实战
  • OpenClaw 2026 版本迭代解析:记忆热插拔与企业落地实践
  • 2025 前端框架趋势:React 领跑,Vue 稳健,Angular 深耕企业级
  • RS485 收发器在 FPGA 中的应用及注意事项
  • 802.11a WiFi 通信算法全场景仿真:全调制与全编码速率匹配
  • VS Code 配置 GitHub Copilot Agent Skills 实战指南
  • Windows 系统永久配置 Git 使用 SSH 连接 GitHub 教程
  • VSCode GitHub Copilot 接入 OpenAI 兼容自定义模型
  • 金仓数据库 MySQL 迁移:语法兼容与语义一致性实践
  • IDEA 集成 AI 插件快速构建 Java 全栈功能实战
  • 构建本地 AI 客服:利用 FastGPT 与 Ollama 打造智能助手
  • Midjourney 第三方 API 的实现方式与合规边界
  • 基于 Stable Diffusion 的 AI 视频生成与背景替换教程
  • Altera FPGA Avalon 总线接口规范简介
  • 零基础入门网络安全:学习路径与岗位方向解析
  • 大模型落地应用:企业智能化转型的策略与实践
  • SAM 3 开源大模型部署教程:Docker 镜像、Jupyter 与 Web 三模式
  • 前端监控实战:基于 Sentry 的错误与性能管理
  • 2026 年各大高校 AIGC 检测政策汇总
  • OpenClaw 接入飞书:配置机器人实现文档与表格自动化

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online