【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命令解释器的主要内容,如果未能解决你的问题,请参考以下文章

简单介绍linux中的shell脚本

Linux实现简易的Shell命令行解释器

shell学习笔记

shell实现SSH自动登陆

Linux命令——Shell程序设计一(变量与操作符)

Linux命令基础