本文从零实现Linux指令系列文章,其他系列文章:
文章目录
shell概述
熟悉linux的人应该都对shell有一个感性的认识,大概知道它是什么~我们还可以用一些更加精确的语言给它一个定义:命令行解释器( CLI) 或shell是代表其用户运行其他程序的程序。shell 反复打印提示例如$
,等待用户输入,然后执行用户的操作。不用shell的linux是没有灵魂的
用户输入的命令行是由空格分隔的 ASCII 单词序列。命令行中的第一个单词要么是内置命令
的名称,要么是可执行文件
的名称。剩下的词是命令行参数。
linux发行版例如ubuntu自带的shell一般是bash,除了bash以外,还有一些很好用的shell工具,这里强推zsh。
本项目希望能够实现一个迷你的shell,功能也许不强大,但应该具有基本的shell功能,例如ls, cd, pwd, path, 并发执行以及重定向等功能。本文首先介绍各模块的实现,最后说明如何运行并调试本项目。
完全掌握整个项目代码的时间:30h
开始使用 Shell 项目
如果有厉害的大手子想直接审查一下笔者写的代码,可以直接访问github:https://github.com/SFUMECJF/linux-command
0 练手任务
斐波那契数列大家都知道是什么,不过我们这里使用c语言中的fork() / wait()以递归方式编写。每个子进程将其结果返回给父进程,父进程一直等到子进程完成。那么通过这个练习要掌握父子进程通信的方式。
如果不限制必须使用多进程 的方式的话,实现是很简单的:
int helper(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else {
return helper(n - 1) + helper(n - 2);
}
}
那么用进程怎么办呢?
实现思路如下:
- 每次递归调用函数的时候,都开一个新进程调用递归函数。
- 父进程给子进程可以直接传参,子进程给父进程的返回值这里利用exit()函数,也即如果子进程调用 exit(0)结束,则父进程可以通过
wait(&return_value)
等待子进程结束,然后再利用sum += WEXITSTATUS(return_value);
将相应子进程的返回值收集到我们想要的结果上。
实现结果:
make fib #编译
unix> fib 3
2
unix> fib 10
55
1 shell 骨架
本节练习系统调用,strtok、strcmp和execv,完成的目标是解析输入的指令以及实现内部指令。
1.1 REPL
任何 shell 的核心都是 REPL,即read-evaluate-print loop。这是一个重复执行以下三个动作的循环:
- 从用户(或从用户指定的脚本)读取输入。
- 评估输入,弄清楚用户想要做什么并去做。
- 打印相关的任何输出。
本项目在行首循环打印utcsh>
,然后使用getline读取用户的输入到c语言的字符串数组。读取数组之后,使用strtok对数组按照空格拆分,就可以获取每个单独的单词了~也就是命令以及参数。
1.2 内部命令
-
exit:该指令退出shell程序。使用系统调用exit(0)完成
-
cd:cd总是只接受一个参数。使用系统调用chdir()完成。
-
path:path命令接受零个或多个参数,每个参数由空格分隔。典型的用法可能如下所示:
utcsh> path /bin /usr/bin
需要在代码内部维护一个二维字符串数组,每次更新的路径放到数组中。路径是为了在执行外部命令时能够找到相应的可执行程序。
1.3 外部命令
如果给出的命令不是三个内置命令之一,则应将其视为外部可执行程序。
对于这些外部命令,使用 fork-and-exec 方法执行程序。每一个外部指令,都利用系统调用execv()调用一个子进程来执行。这里execv如何能够找到外部可执行程序呢,要靠我们上面维护的路径二维数组。
对于父进程:父进程应该使用wait()或waitpid()等待子进程。
1.4 读入shell脚本文件
除了手动输入命令之外,还需要能够处理文件中的大量脚本。这里练习的文件操作,打开文件,读取一行,注意这里不是从标准输入读取,而是从文件中读取。
1.5 重定向
很多时候,shell 用户更喜欢将程序的输出发送到文件而不是屏幕。shell支持使用 > 以及 < 进行输入和输出的重定向。这里使用open()以及dup1和dup2完成重定向到文件的功能。
1.6 并发执行命令
utcsh> cmd1 & cmd2 & cmd3 args1
输入以上命令时, shell 应该并发执行cmd1,cmd2和cmd3(无论传递了什么参数) ,然后等待它们中的所有命令完成。
然后,一旦所有子进程都已启动,父进程必须使用wait()或waitpid() 确保所有进程都已完成,然后再继续。
1.7 测试与运行
运行:make
调试:make debug
之后使用gdb或者vscode配置好环境用ide调试
测试代码功能是否正确,总共有32个测试。make check
搞定所有测试,make testcase id=3
单独测试第3条。
1.8 运行截图
1.9 收获与想法
很久之前我做robomaster比赛,学习了CMake以及c++调用Open CV库的基本操作。但当我去字节实习面试的时候,只记得面试官很诧异地问我:啊,你的代码里都没有系统调用吗?
(那时我只会打开文件,而且并不知道文件操作也属于系统调用的一部分)~
希望大家Linux开发的知识都多多的~
互相交流
读者你好!如果你对本文内容感兴趣,我十分希望能够和你互相学习,可以扫码和我联系!一起进步