- 代表具体Unix小程序的命令 。
- Pipe符号"|" 。
数学式子里的括号 , 其实它无关紧要 , 括号只是给人看的 , 它规定一些运算的优先级顺序 , 这叫 中缀表达式 , 一个中缀表达式可以轻松被转换为 前缀表达式 , 后缀表达式 , 从而消除括号 。事实上 , Unix的Pipe最初也面临过这样的问题 , 到底是中缀好呢 , 还是前/后缀好呢?我们现在使用的Unix/linux命令 , 以cp举例:
cp $in $out这是一个典型的前缀表达式 , 但是当pipe的发明者McIlroy最初引入pipe试图组合各个程序时 , 最初上面的命令行被建议成:
$in cp $out就像我们的(3 + 5) × 8 一样 。但是这非常不适合计算机处理的风格 , 计算机不得不首先扫描解析这个式子 , 试图:
- 理解 “括号括起来的要优先处理” 这句复杂的话;
- 区分哪些是输入 , 哪些是操作符…
× 8 + 35就是(3 + 5) × 8 的前缀表达式 , 可以看到 , 没有了括号 。对于pipe组合程序而言 , 同样适用于这个原则 。于是前缀命令成了pipe组合命令的首选 , 现如今 , 我们可以用:
pro1 $stdin|pro2|pro3|pro4|...|proX $stdout轻松组合成任意复杂的逻辑 。
Pipe协同组合程序的Unix原则是一个创举 , 程序就是一个加工过滤器 , 它把一系列的输入经过自己的程序逻辑生成了一系列的输出 , 该输出又可以作为其它程序的输入 。
在Unix/Linux中 , 各种shell本身就实现了这样的功能 , 但是为了彻底理解这种处理方式的本质 , 只能自己写一个才行 。来写一个微小的shell吧 。
再次看上面提到的Unix Pipe的处理序列:
pro1 $stdin|pro2|pro3|pro4|...|proX $stdout如果让一个shell处理以上组合命令 , 要想代码量少 , 典型方案就是递归 , 然后用Pipe把这些递归调用过程给串起来 , 基本逻辑如下:
int exec_cmd(CMD *cmd, PIPE pipe){ // 持续解析命令行 , 以pipe符号|分割每一个命令 while (cmd->next) { PIPE pp = pipe_create(); if (fork() > 0) { // 父进程递归解析下一个 exec_cmd(cmd->next, pp); return 0; } // 子进程执行 dup_in_out(pp); exec(cmd->cmdline); } if (fork() > 0) { wait_all_child(); return 0; } else { dup_in_out(pp); exec(cmd->cmdline); }}按照上面的思路实现出来 , 大概60行左右代码就可以:
// tinysh.c// gcc tinysh.c -o tinysh#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>#define CMD_BUF_LEN 512char cmd[CMD_BUF_LEN] = {0};void fork_and_exec(char *cmd, int pin, int pout){ if (fork() == 0) { if (pin != -1) { dup2 (pin, 0); close(pin); } if (pout != -1) { dup2 (pout, 1); close(pout); } system(cmd); exit(0); } if (pin != -1)close(pin); if (pout != -1)close(pout);}int execute_cmd(char *cmd, int in){ int status; char *p = cmd; int pipefd[2]; while (*p) {switch (*p) {case '|':*p++ = 0;pipe(pipefd);fork_and_exec(cmd, in, pipefd[1]);execute_cmd(p, pipefd[0]);return 0;default:p++;} } fork_and_exec(cmd, in, -1); while(waitpid(-1, &status, WNOHANG) != -1); return 0;}int main(int argc, char **argv){ while (1) {printf("tiny sh>>");gets(cmd);if (!strcmp(cmd, "q")) {exit(0);} else {execute_cmd(cmd, -1);} } return 0;}下面是执行tinysh的结果:
[root@10 test]# ls -l总用量 28-rw-r--r-- 1 root root 0 9月 1 05:39 a-rwxr-xr-x 1 root root 9000 9月 1 05:38 a.out-rw-r--r-- 1 root root 0 9月 1 05:39 b-rw-r--r-- 1 root root 0 9月 1 05:39 c-rw-r--r-- 1 root root 0 9月 1 05:39 d-rw-r--r-- 1 root root 0 9月 1 05:39 e-rwxr-xr-x 1 root root 9000 9月 1 05:38 tinysh-rw-r--r-- 1 root root 1167 9月 1 05:38 tinysh.c[root@10 test]# ./tinyshtiny sh>>ls -l |wc -l9tiny sh>>cat /etc/inittab |grep init# inittab is no longer used when using systemd.tiny sh>>cat /etc/inittab |grep init|wc -l1tiny sh>>q[root@10 test]#递归解析的过程中fork/exec , 一气呵成 , 这就是一个最简单shell实现 。它可完成组合程序的执行并给出结果 。
这个tiny shell命令解析器的逻辑可以表示如下:
推荐阅读
- 不需要软件,如何简单实现内外网自由切换?
- 基于windows服务器下的文件备份实现方案
- python使用SocketServer实现网络服务器
- 并使用java实现 一文彻底看懂Base64编码解码原理
- Redis中的发布订阅模式用代码实现就是这么简单
- 使用原生的js实现简易的图片延时加载
- 使用python实现UDP编程
- Go语言实现LeetCode算法:958 检查二叉树的完整性
- PHP实现站内信
- SSH工作过程
