A Simple Custom Shell

实验要求

√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 Variablesback_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函数* argv[]) {//default no need to relocationflag = 0;//default not in the backback_flag = 0;char *command;while (1) {fgets(line, MAX_SIZE, stdin); command = strtok(line, ” \n\t();”);if (command == NULL) {continue;//if user input nothing, then ingore it.} else if (strcmp(command, “exit”) == 0) {//if user input “exit” the exit the application.my_exit();} else if (strcmp(command, “cd”) == 0) {my_chdir(command);} else {//let system handle other commandsmy_unix(command);}}return 0;}

main函数中首先把两个flag置0,表示默认普通情况。 然后开一段死循环,在死循环中通过fgets从标准输入读一行命令。然后按照之前所述,通过strtok将命令分割,拿到第一段字符串(命令主体),根据不同的命令调用不同的函数。 注意:1、循环开始那块有个被注释掉的部分,这是Linux系统下的函数,MacOS下没有。 2、这段程序在Ubuntu,Codeblocks下运行得挺好,在Xcode下接收第二个输入开始就变成死循环,使用fgets、scanf都会这样,不知道为什么。

my_exit#pragma mark – Other Functionsvoid my_exit() {// printf(“exit”);pid_t pid = getpid();//SIGTERM: software termination signal from killkill(pid, SIGTERM);}

这段函数非常简单,将当前进程杀死即可。pid为进程的id号,标识该运行的进程。SIGTERM标识的意思是由软件引起的终止信号。

效果图:

my_chdir()void my_chdir(char *target) {// printf(“cd”);int status;//in order to get next token and to continue with the same string.//NULL is passed as first argumenttarget = strtok(NULL, ” \n\t();”);while (target) {status = chdir(target);//handle errorif (status < 0) {fprintf(stderr, “Error in my_chdir(%s) : %s\n”, target, strerror(errno));return;}target = strtok(NULL, ” \n\t();”);}}偶尔也要现实和虚伪一点,因为不那样做的话,很难混。

A Simple Custom Shell

相关文章:

你感兴趣的文章:

标签云: