一个用 C 语言从零开始编写的 Linux Shell,支持管道、重定向、别名、历史记录等功能。
- 执行外部命令(
ls、cat、grep等) - 内置命令:
cd、exit、history、alias - 管道(
|),支持多级管道 - 输出重定向(
>) - 引号处理(
"和') - 历史记录与扩展(
!!、!数字) - 别名系统(
alias/unalias) - 配置文件(
~/.lshrc) ~展开为用户家目录
# 编译
gcc -o lsh main.c
# 运行
./lsh
# 创建配置文件(可选)
cat > ~/.lshrc << EOF
alias ls='ls --color=auto'
alias ll='ls -la'
alias grep='grep --color=auto'
alias ..='cd ..'
EOF# 基本命令
/home/user> ls -la
/home/user> cd /tmp
# 管道
/home/user> ls | grep main | wc -l
# 引号
/home/user> echo "hello world"
/home/user> grep "main" file.txt
# 别名
/home/user> alias ll='ls -la'
/home/user> ll
# 历史
/home/user> history
/home/user> !! # 执行上一条命令
/home/user> !5 # 执行历史中第 5 条命令
# 重定向
/home/user> ls > output.txt从 main 函数和 lsh_loop 开始,实现了最基础的 REPL(Read-Eval-Print Loop):
lsh_read_line:使用getline读取用户输入lsh_split_line:使用strtok分割命令和参数lsh_launch:使用fork + execvp执行外部命令lsh_execute:调度内置命令和外部命令
实现了三个基础内置命令:
cd:使用chdir切换工作目录exit:退出 Shellhistory:查看命令历史
这是最核心也最复杂的部分。
核心概念理解:
- fd(文件描述符):进程内部的文件编号,是内核打开文件表的索引
- pipe:内核中的内存缓冲区,有读端和写端
- dup2:复制文件描述符,让管道端口"伪装"成标准输入/输出
- fork 复制 fd:子进程继承父进程的文件描述符表
管道实现:
ls | grep main | wc -l
cmd0 (ls) cmd1 (grep) cmd2 (wc)
stdout → pipe0 → stdin
stdout → pipe1 → stdin
踩过的坑:
- 后置自增 vs 前置自增:
cmd_starts[cmd_index++]覆盖了第一个命令的指针,导致管道无输出 - 循环变量混淆:
cmd_starts[j]错写为cmd_starts[i],数组越界 - 管道关闭不彻底:父子进程都要关闭不用的管道端口,否则读端收不到 EOF 会永久阻塞
fflush(stdout):提示符不以\n结尾时,需要手动刷新缓冲区
用状态机重写了 lsh_split_line:
- 遇到
"或'进入引号模式 - 引号内的空格不作为分隔符
- 每个 token 独立
malloc,不再依赖strtok原地修改
实现了 ~/.lshrc 配置文件和别名展开:
- 键值对映射:
alias ll='ls -la'存储为key="ll",value="ls -la" - 只在命令位置展开别名(管道每个命令的第一个 token)
alias和unalias内置命令
>输出重定向:使用open+dup2将 stdout 重定向到文件~展开:替换为HOME环境变量preprocess_pipe:在|前后自动添加空格,支持ls|grep无空格写法
管道是共享资源,所有持有端口的进程都关闭后管道才真正关闭。如果子进程不关闭不用的端口:
- 读端收不到 EOF(因为还有进程持有写端)
- 文件描述符泄漏
WIFEXITED 和 WIFSIGNALED 才是真正的退出。Ctrl+Z 会让进程进入 WIFSTOPPED 状态(暂停),此时不应该认为进程已退出。
args 数组在 free_tokens 时会被释放,别名表中的指针如果不复制就会变成野指针。
- 输入重定向
< - 追加输出重定向
>> - 后台运行
& - 环境变量
$HOME、$PATH - 通配符展开
* - Ctrl+C 不退出 Shell
- Tab 补全
- Tutorial - Write a Shell in C by Stephen Brennan
- APUE(Advanced Programming in the UNIX Environment)