本文主要是介绍shell简易版编写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一、制造命令行提示符
二、获取命令
三、分割命令
四、执行命令
五、内建命令
全部代码
一、制造命令行提示符
命令行主要有三个组成:用户名、主机名和路径。这也是需要优先获取的。
将每块功能封装起来,这样阅读性高一些。
这三个都存放在环境变量里,可以通过getenv去获取。
对应的环境变量:
- 用户名:USER
- 主机名:HOSTNAME
- 路径:PWD
将这三个字符串放入到line中,使用snprintf函数。
命令行是不能加\n的,我们要在命令行后面输出内容,通过刷新缓冲区的方式来实现。
scanf代替一下获取命令,能更好看出制造命令行的效果。原命令行是$结尾,这里使用>结尾做区分。
基本和原命令行是一样的,不过路径因该是只保留最后一个,后面再对这个进行更改。
二、获取命令
输入内容使用fgets,它可以以\n为结束读取的字符。
可以看到确实读到了空格,但打印的usercommand是多了一行,%s\n因该是只换行一次。这是因为fgets把\n也读了进去,我们需要把字符串最后一个字符给改一下(\n是一个字符,不是\和n两个字符)。
在输入命令时输入错误,需要Ctrl+Backspace才能删除。
三、分割命令
命令需要分割成argv字符串数组一样进行使用。定义一个全局的Argv存放。
for是为了测试。分割字符串有对应的函数strtok来分割。
使用while进行这样的循环赋值,strtok再分割完后会返回NULL,刚好符合Argv以NULL结尾,也可以时循环停止。这里多加一层()是为了不报wrning。
四、执行命令
只执行PATH内的可执行文件,使用execvp替换函数。再将这几个功能一起循环就有一个简易shell效果了。
现在就能完成一些PATH内的功能,不过对于内建命令还不能使用。
五、内建命令
内建命令指的是那些shell不需要创建子进程的命令,内建命令就是shell内的一段代码。如cd、echo [环境变量]等。
cd测试:
cd是改变当前的工作目录,如果是让子进程来执行那就是子进程的工作目录发生了改变,父进程的并没有。echo $[环境变量]也是子进程查看的是自己的环境变量,不是父进程的,父进程的内建命令在使用时会改变一些环境变量需要自己维护。
单独判断内建命令
内建命令的判断非常朴实,有几个就使用几个if。
echo $?:拿到最近一次进程的退出信息。只需要将lastcode设为全局变量,就可以拿到上一次的退出信息,需要注意在用户的角度echo也是在执行文件,所以需要将lastcode值改为0,视作echo的退出信息。
cd:更改路径可以使用chdir,修改完路径环境变量PWD也需要跟着修改,不过不能直接拿输入的路劲来修改,PWD需要绝对路径,cd会有一些特殊的符号来表示一些路劲(~:家目录;-:上一次所处路劲;不跟任何路劲和符号:家目录),这个需要单独处理。更改路劲后先通过getcwd拿到当前路径,再将当前路劲以环境变量的形式存起来。
运行结果:
现在在来处理一下命令行提示符中路劲的问题,要只显示当前所属目录名。
要在获取路劲后修改,找最后一个目录。只需要从字符串尾向前找到第一个/就可以,使用一个宏函数来封装。
使用{}是为了让宏函数在使用时,能在结尾加;看起来规范些。
原shell在命令行是没有/的,打印路劲时也只打印/后面的内容,cwd+1就可以。不过这会导致在跟目录下来回打印/需要单独判断。
运行结果:
全部代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<errno.h>#define Size 512
#define SkipPath(p) { p += (strlen(p)-1); while(*p != '/') p--;}
char* Argv[32];
char cwd[Size*2];
int lastcode;const char* GetUserName()
{const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHomeName()
{const char* name = getenv("HOSTNAME");if(name == NULL) return "None";return name;
}const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}const char* GetHome()
{const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}void MakeCommandLineAndPrint()
{char line[Size];//存放命令行//获取用户名const char* username = GetUserName();//获取主机名const char* hostname = GetHomeName();//获取路径const char* cwd = GetCwd();SkipPath(cwd);//让cwd去指向最后一个/的位置//结合起来并打印snprintf(line,sizeof(line),"[%s@%s %s]> " ,username, hostname, strlen(cwd)==1 ? "/" : cwd+1);printf("%s",line);fflush(stdout);
}int GetUserCommand(char* usercommand, size_t size)
{if(fgets(usercommand, size, stdin) == NULL){return -1;}usercommand[strlen(usercommand)-1] = '\0';return strlen(usercommand);
}void SplitCommand(char* usercommand)
{Argv[0] = strtok(usercommand," ");int i = 1;while((Argv[i++] = strtok(NULL," ")));
}void ExecuteCommand()
{pid_t id = fork();if(id < 0){perror("fork");//创建失败exit(1);}else if(id == 0){execvp(Argv[0],Argv);//去PATH中查找可执行文件exit(errno);//子进程走到这里就表示替换失败,也就是命令执行失败}int status = 0;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode = WEXITSTATUS(status);if(lastcode != 0){//命令执行错误,打印错误信息printf("%s:%s:%d\n", Argv[0], strerror(lastcode), lastcode);}}
}void Cd()
{const char *path = Argv[1];if(path == NULL || strcmp(path,"~") == 0) path = GetHome();//处理特殊符// 这时path 一定存在路劲chdir(path);//更改路径char temp[Size*2];getcwd(temp, sizeof(temp));//拿到当前绝对路劲//cwd是保存的全局变量,除了env内要有绝对路劲,程序内部也需要保存使用snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}int CheckBuildin()
{char* s = Argv[0];int ret = 0;if(strcmp(s,"cd") == 0){ret = 1;Cd();}else if(strcmp(s,"echo") == 0 && strcmp(Argv[1],"$?") == 0){ret = 1;printf("%d\n", lastcode);lastcode = 0;}return ret;
}int main()
{while(1){//制造命令行MakeCommandLineAndPrint();//获取命令char usercommand[Size];int n = GetUserCommand(usercommand,sizeof(usercommand));//将命令放入usercommandif(n == -1) return 1;//获取失败//分割命令SplitCommand(usercommand);//判断是否是内建命令if(CheckBuildin())//是内建返回真,会在函数内处理执行{continue;}//执行命令ExecuteCommand();}return 0;
}
这篇关于shell简易版编写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!