前言
这篇文章会从头开始使用C
语言编写一个可以交互的SHELL,并为其添加以下功能:
- 运行可执行文件
- 输入中断退出
- cd/export/env/exit 内建命令
- 实现了
|
管道通信 - 实现了部分内部命令自动高亮
ls/grep/cat
- 记录历史命令
- 检查运行环境
未实现功能:
- & 后台任务 / fg / bg 恢复执行
- && 依赖任务
- || 多任务
/ >> 重定向
任务要求
- (1) 实现一个模拟的shell
- (2) 实现一个管道通信程序
其实书上的写的任务里这些是独立的几个任务,但是我没注意看书上的要求,于是把(1)(2)都写在了SHELL中,也就是说我实现了一个可以使用
1 | cat /path/to/file | grep symbel | grep symbel2 |
类似机制的指令的SHELL。这里记录、分析一下用到的一些系统调用/库函数。
前提
使用了readline
库,并且使用了Linux
管道调用,所以不能在Windows
上复现。
安装readline
库:
1 | sudo apt update && sudo apt install libreadline-dev |
创建简单的SHELL部分
对于shell,最重要最简单的功能就是能够读懂用户的指令了,比如最简单的:
1 | ls |
又或者复杂一些的命令:
1 | ls | grep pro |
我们的主函数的逻辑就像下面的结构:
1 | int main() { |
信号处理
从上往下看,首先第一个函数setup_signal_handlers
,你需要在这里处理shell的信号机制,比如用户的中断信号Ctrl+C
。普通的程序会使用正常的中断退出,但是作为一个shell,不应该在用户发出Ctrl+C
中断信号的时候退出,而是应该处理Ctrl+D
的输入终止信号才退出,所以我们可以得到一个简单的setup_ignal_handlers
函数的定义:
1 |
|
这里的signal
函数很重要,它的函数声明和使用示例为:
1 | // reference |
具体的细节可以参考菜鸟教程:C 库函数 – signal() | 菜鸟教程
我们只需要忽略掉SIGINT
信号即可。
正则补全
然后是下面这行神奇的代码:
1 |
|
右边是一个形如:
1 | typedef char **rl_completion_func_t(const char * test, int start, int end) |
的函数指针。该函数需要处理用户输入的不完整字符串的补全结果。readline
库提供了一个正则匹配的函数:
1 | extern char **rl_completion_matches PARAMS((const char *, rl_compentry_func_t *)); |
上面的示例中,我们需要定义一个正则规则函数,形如:
1 | char *command_generator(const char *text, int state); |
的匹配函数,在这个函数中返回我们补全的结果。如果想要了解更多Readline
中的补全机制,可以参考这篇博客。也可以直接读我的源码。
ReadLine自动补全分析 - LiuYanYGZ - 博客园
其实re_attempted_aompeltion_function
类似于一个回调函数,会在用户按下Tab
的时候尝试补全。
命令处理主体
再往后看,就是我们的函数主体了,在while循环中,我们需要处理用户的Enter
输入,并执行对应的操作。并且需要为用户提供当前运行环境的一些信息,包括用户名、机器信息、路径信息等。这部分操作我们在get_prompt
中实现,你可以随心所欲地实现你想要的提示词:
prompt
1 | char *get_prompt() { |
这里调用了getpwuid
、getuid
、getcwd
三个函数,你需要引入这些头文件:
1 |
add_history
add_history
是readline
库提供的一个保存命令历史的函数,允许用户使用上下来查阅历史记录。因为上下箭头其实输入的也是一个字符串,可以判断并执行对应的操作。
execute_command
执行命令!到这里,用户已经输入了一个完整/不完整的命令,并按下了回车键,他希望你能帮他调用这些可执行文件,并得到对应的输出/或重定向到其他地方。首先允许我为你介绍一个分词函数:
1 | char * strtok (char *str, const char * delimiters); |
需要注意的是,这个函数会将第一参数分割,所以在处理它的时候需要进行复制处理。
下面是一个简单的循环取词的示例:
1 | char * input = "I need a friend."; |
更多有关strtok的信息,请参考:(十六)strtok、strtok_s、strtok_r 字符串分割函数 - xtusir - 博客园