实验要求
√1、模拟Linux Shell的运行样子
√2、可执行Linux文件系统中的命令(外部命令),如:ls, mkdir….
√3、可执行自定义的内置Shell命令,如: chdir, clear, exit
√4、支持命令后台运行,将尾部有&号的命令抛至后台执行
附加:
(待)1、实现Shell对管道的支持,如支持 ls | grep “pipe” 等命令
(待)2、实现Shell对输入输出重定向的支持,如支持 ls > result.txt
多啰嗦一句
虽然最终是要在Ubuntu上运行,也在Ubuntu上安装Codeblocks了,但是还是感觉各种难用。。。所以我决定在Xcode下写,搬到Ubuntu上运行+_+自虐了一晚上,才做出前四个,后面的实在有心无力了~ ~
结果图
正文
源码
本文源码挂在github上,url:https://github.com/YiZhuoChen/MyShell,需要的可以自行下载。
原理
实验的基本思路是,不断从命令行接收用户输入,将用户输入的字符串分割(可能带有参数)。
——对于内置命令,分别判断然后到自定义函数中实现,有的可以调用Linux提供的函数,有的可以自己实现。
——对于外部命令,处理时需要判断是否要扔到后台、重定向,是否用到管道等。普通情况下,fork出子进程,然后在子进程中调用execvp函数处理,父进程中wait即可。
——字符串的分割:理论上应该自己写一个算法,对用户的输入进行合理性检验,同时分割字符串,方便起见,这里采用string提供的方法strtok简单处理,strtok是根据提供的字符集中所有字符对输入字符串进行分割,返回指向第一个字符串的指针,接下来还想对同一个字符串分割的话,第一个参数传入NULL即可。
——后台进程:父进程fork出子进程后,会返回子进程的pid,同时子进程的ppid置为父进程的pid(类比链表),这样父子进程就形成了关联。父进程中调用wait后就等待子进程结束返回的信号。所谓后台进程,就是让父进程不再关心子进程,将子进程托管给God Progress(即子进程的ppid = 1),由操作系统来管理。简单来说,就是父进程中不需要wait,也不需要处理子进程的返回信号了。
其他的代码中在详述。
全局变量与函数
首先是导入一些头文件,以及全局变量和函数的声明:
#include <stdio.h>
#include <string.h> //strcmp, strtok
#include <unistd.h> //getpid, chdir, _exit
#include <signal.h> //kill
#include <errno.h> //errno, EINTR
#include <stdlib.h> //EXIT_FAILURE
#define MAX_SIZE 100
#pragma mark - Global Variables
char line[MAX_SIZE]; //get user input from command line
int flag; //support relocation or not? ie. ls > result.txt
int back_flag; //run the progress in the back?
#pragma mark - Functions Declaration
/**
* exit terminal
*/
void my_exit();
/**
* command "cd", go to target directory
*
* @param target target directory
*/
void my_chdir(char *target);
/**
* other commands. Let linux to run it.
*/
void my_unix(char *command);
工程中用到的每个头文件中的函数或者变量已经写到头文件后的注释中了。
line用于接收用户从终端输入的命令。
flag用于标识重定向操作,接下来会对重定向进行简单的模拟。但并未实现。
back_flag用于标识是否进行后台操作。依据是用户输入的命令后缀是否有&符号。
my_exit()是自定义exit命令处理函数。
my_chdir(char *target)是自定义cd命令处理函数,target是目标目录
my_unix(char *command)是调用Linux文件系统中的命令,command为用户输入的命令,在该函数中同时处理重定向和后台操作。
main函数
#pragma mark - Main
int main(int argc, const char * argv[]) {
//default no need to relocation
flag = 0;
//default not in the back
back_flag = 0;
char *command;
while (1) {
//#warning Not available in MacOS
// char *userName = getlogin(); //user name
// char *path = get_current_dir_name(); //currentPath