【Linux】实现一个简单的shell命令解释器
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【Linux】实现一个简单的shell命令解释器相关的知识,希望对你有一定的参考价值。
参考技术A 姓名:罗学元 学号:21181214375 学院:广州研究院【嵌牛导读】shell命令解释器该包含哪些部分
【嵌牛鼻子】shell命令解释器该包含哪些部分
【嵌牛提问】shell命令解释器该包含哪些部分
我们所做的这个简单的shell命令解释器可以实现简单的常用的基本命令,如ls、pwd、cd、cd - 、cd ~ 等
根据简单命令的定义,它的第一个参数是要执行的命令,后面的参数作为该命令的参数。
要执行的命令有两种情况:
一种是外部命令: 也就是对应着磁盘上的某个程序,例如 pwd、ls等等。对于这种外部命令,我们首先要到指定的路径下找到它,然后再执行它。
另一种是内部命令:内部命令并不对应磁盘上的程序,例如cd等等,它需要shell自己来决定该如何执行。例如对 cd 命令,shell就应该根据它后面的参数改变当前路径。
对于外部命令,需要创建一个子进程来执行它,本质就是fork+exec
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX 10
#define STRLEN 128
#define PATH "/bin/" //系统bin路径位置
char OLDPWD[STRLEN]=0; //记录上一次的命令,为了cd -这条命令
//================================================================================
//每次敲回车输出当前所在用户信息
//普通用户和root用户的提示符区别
void Printf_Info()
char flag='$';
struct passwd *pw=getpwuid(getuid());
assert(pw!=NULL);
//uid为0则为root用户
if(pw->pw_uid==0)
flag='#';
struct utsname hostname; //主机名
uname(&hostname);
char node[STRLEN]=0;
strcpy(node,hostname.nodename); //获取网络上的名称
char* name=strtok(node,".");
//获取绝对路径
char path[STRLEN]=0;
getcwd(path,STRLEN-1);
char*p=path+strlen(path); //p指向绝对路径的末尾
while(*p!='/')
p--;
//p指向路径末尾往前的第一个‘/’位置处
if(strlen(path)!=1)
p++; //++前,p->'/'
if(strcmp(path,pw->pw_dir)==0)
p="~";
printf("\033[;32mMyBash[%s@%s %s]%c\033[0m",pw->pw_name,name,p,flag);
// \033[47;31mThis is a color test\033[0m 设置打印结果的颜色
fflush(stdout);
//================================================================================
void Mycd(char*path)
//第一个字符串为cd而第二为空 如:cd 则结束本轮循环
if(path==NULL)
exit(0);
//cd ~ 回到用户根目录
if(strcmp(path,"~")==0)
struct passwd*pw=getpwuid(getuid());
path=pw->pw_dir;
//cd - 回到上一次的位置
if(strcmp(path,"-")==0)
//若是第一次输入命令,则cd -命令不存在!
if(strlen(OLDPWD)==0)
printf("\033[;31mMyBash:cd:OLDPWD not set\n\033[0m");
return ;
//否则把上一次的命令给path
path=OLDPWD;
//getpwd记录当前工作目录的绝对路径
char oldpwd[STRLEN]=0;
getcwd(oldpwd,STRLEN-1);
if(-1==chdir(path))//反之则不是空,则通过chdir系统调用进入到该目录中
char err[128]="\033[;31mMybash: cd \033[0m";
strcat(err,path);
perror(err);
//每次执行完cd命令后,把工作路径赋给OLDPWD
strcpy(OLDPWD,oldpwd);
//================================================================================
//命令分割函数
void Strtok_cmd(char*buff,char*myargv[])
char *s=strtok(buff," "); //分割输入的字符串
if(s==NULL) //如果s为空,则进入下一轮循环
exit(0);
myargv[0]=s; //把分割出来的第一个字符串放在myargv[0]中
int i=1;
while((s=strtok(NULL,""))!=NULL) //把后续分割出来的字符串依次存放在数组中
myargv[i++]=s;
//===============================================================
int main()
while(1)
char buff[128]=0;
Printf_Info();
//从终端获取命令存入buff中
fgets(buff,128,stdin);
buff[strlen(buff)-1]=0;
char *myargv[MAX]=0;
//分割输入的命令
Strtok_cmd(buff,myargv);
//如果输入exit则退出循环
if(strcmp(myargv[0],"exit")==0)
exit(0);
//如果分割出来的第一个字符串为cd
else if(strcmp(myargv[0],"cd")==0)
Mycd(myargv[1]);
continue;
//若是系统调用,直接替换fork+exec
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
char path[256]=0;
if(strncmp(myargv[0],"./",2)!=0 && strncmp(myargv[0],"/",1)!=0)
//先把路径放入path中
strcpy(path,PATH);
//进行命令拼接,路径+名称
strcat(path,myargv[0]);
//替换进程 例如:/bin/ls
execv(path,myargv);
perror("\033[;31mexecv error\033[0m");
//处理僵死进程
else
wait(NULL);
运行结果如下 :
异常处理如下:
若是第一次运行程序,则不能使用cd - 命令,因为此时还没有历史路径
若进入一个不存在的目录则会报错,没有这个文件或目录
若直接输入一个不存在的无法识别的命令,也会报错。
Linux实现简易的Shell命令行解释器
大家好我是沐曦希💕
文章目录
一、前言
前面学到了进程创建,进程终止,进程等待,进程替换,那么通过这些来制作一个简易的Shell命令行解释器。
首先这是与Shell的互动:
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
二、准备工作
1.输出提示符
这里的提示字符为用户名@主机名 当前路径# 直接打印出来作为提示所用
printf("用户名@主机名 当前路径#");
这里没有\\n,会有缓冲区的问题,类似于我们之前所说的进度条所遇到的问题,可以用fflush(stdout)刷新缓冲区。
2.输入和获取命令
- 输入
我们需要输入一连串命令,其中可能出现空格,所以不能使用gets函数,需要用到fgets函数,同时,可以定义一个lineCommand[NUM]数组
#define NUM 1024
char lineCommand[NUM];
char* s = fgets(lineCommand,sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
但是打印的时候却多换了一行,这是我们把\\n也读取到了,直接进行处理即可,清除最后一个\\n
lineCommand[strlen(lineCommand) - 1] = 0;
可以通过打印看看效果和测试是否有BUG
printf("test:%s\\n",lineCommand);
- 获取
输入之后,我们自然需要去进行获取,我们需要分割命令行,这个地方用strtok。把字符串切割成若干个子串:
strtok:第一次直接传递参数,第二次则必须传NULL。且在最终strtok会返回NULL。
3.shell运行原理
同时,在理解一下shell的运行原理:shell内部提取命令行做分析,然后调用exec. shell执行命令必须通过创建子进程,如果不创建子进程会把我们所有的shell全部替换,所以执行命令时一般磁盘上的程序必须创建子进程。
4.内建命令
我们在运行自己写的shell的时候,发现输入cd …输入cd path等命令时发现路径并没有改变!
没有发生改变是因为自己写的shell执行很多命令都要fork()创建子进程,让子进程执行的cd,子进程有自己的工作目录,所以更改的子进程的目录,子进程执行完毕,继续用的是父进程,既shell,并没有影响父进程,所以并没有改变。
对于cd,我们可以采用内建命令:不需要创建子进程执行,让shell自己执行命令,称为内建命令。本质就是执行系统接口,我们可以调用一个系统接口chdir,可解决上述问题:
5.替换
采用execvp进行替换进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
execvp(myargv[0],myargv);
exit(1);
三、整体代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
char lineCommand[NUM];
char *myargv[OPT_NUM];//指针数组
int lastcode = 0;
int lastsig = 0;
int main()
while(1)
// 1.输出提示符
printf("lj@VM-8-2-centos 当前路径#");
fflush(stdout);
// 2.获取用户输入的命令,输入的时候,用户最后还输入了\\n
char* s = fgets(lineCommand,sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
(void)s; //避免Linux认为s变量未使用,导致警告
// 清除最后一个\\n;例如:abcd\\n
lineCommand[strlen(lineCommand) - 1] = 0;
//printf("test:%s\\n",lineCommand);
// "ls -a -l -i" -->字符串分割-->"ls" "-a" "-l" "-i"
myargv[0] = strtok(lineCommand, " ");
int i = 1;
if(myargv[0] != NULL && (strcmp(myargv[0],"ls") == 0))
myargv[i++] = (char*)"--color=auto";
//如果没有子串了,strtok会返回NULL,即myargv[end] = NULL
while(myargv[i++] = strtok(NULL," "));
//如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
//像这种不需要让我们的子进程来执行,而是让shell自己执行的命令—内建命令
//其中echo是一个自建命令
if(myargv[0] != NULL && (strcmp(myargv[0],"cd") == 0))
if(myargv[1] != NULL) chdir(myargv[1]);
continue;
if(myargv[0] != NULL && myargv[1] != NULL && (strcmp(myargv[0],"echo") == 0))
if(strcmp(myargv[1],"$?") == 0)
printf("%d,%d\\n",lastcode,lastsig);
else
printf("%s\\n",myargv[i]);
continue;
//利用条件编译测试是否成功
#ifdef DEBUG
for(int i = 0; myargv[i]; ++i)
printf("myargv[%d]:%s\\n",i,myargv[i]);
#endif
//执行命令
pid_t id = fork();
assert(id != -1);
if(id == 0)
execvp(myargv[0],myargv);
exit(1);
int status = 0;
pid_t ret = waitpid(id,&status,0);
assert(ret > 0);
(void) ret;
lastcode = (status >> 8) & 0xFF;
lastsig = status & 0x7F;
return 0;
以上是关于【Linux】实现一个简单的shell命令解释器的主要内容,如果未能解决你的问题,请参考以下文章