操作系统实验(1)—— Linux命令解释程序设计与实现

📖 操作系统实验: Linux命令解释程序设计与实现

Linux命令解释程序设计与实现

一、实验目的

​ 探索、理解并掌握操作系统命令解释器的设计原理和实现机制,基于Linux内核进行相应命令解释程序的设计和实现,并在Linux操作系统平台上加以测试验证。

二、实验内容

具体实验内容如下:

​ 分析、设计与实现基于Linux内核的命令解释程序(Shell),主要包括系统环境变量的设置和初始化、系统命令提示符显示、命令辨别解析(区分内部命令与外部命令及不同内部命令)、典型内部命令(譬如显示指定目录下文件列表、显示文本文件内容、文件拷贝、文件删除、空文件创建、日期设置/显示)处理等功能,并在Linux操作系统上测试验证。

​ Linux命令解释程序功能设计要求:

  • 选取和设计实现一组内部命令(五条以上);
  • 外部命令执行采用直接调用exec系统调用的方式来实现;
  • 至少一条内部命令采用直接调用相应系统调用的方式来实现;
  • 系统环境变量(至少包括用户主目录HOME和可执行程序搜索路径目录PATH)支持;
  • 在Linux操作系统上启用(或替换原命令解释程序Shell)并测试验证。

三、实验过程

3.1 实验环境

环境名称下载方式
VMware Workstation 16 Prohttps://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html
ubuntu 20.04.4LTS (64-bit)https://ubuntu.com/download/desktop
GCC编译器终端输入命令sudo apt-get install build-essential
VSCode直接在ubuntu中的软件商店里下载

3.2 总体设计

3.2.1 设计思路
  • 编程语言 : C语言

    选择C语言是因为在linux下有很多系统调用函数可供C语言直接使用,方便实现相关的bash命令。除此之外,我比较熟悉C语言,上手较为容易。

  • 功能设计:

    (1)内建命令

    命令名功能参数个数
    help显示用户手册0
    environ显示所有的环境变量0
    time显示当前时间0
    pwd显示当前路径0
    clr清空屏幕内容0
    exit退出当前进程0
    quit退出自制的shell0
    ls无参数则显示当前目录下的内容,有参数则显示参数所指目录下的内容0、1
    cd无参数则显示当前目录,有参数则改变当前目录为参数内容0、1
    exec使用参数代表的命令替换当前进程1
    unset将参数所指的环境变量的值取消1
    touch创建名为该输入参数的文件1
    set无参数时,显示所有环境变量;有2个参数时,设置第1个参数代表的环境变量的值为第2个参数0、2
    echo无参数则显示空内容,有参数则显示参数内容0、+
    copy将第一个参数文件的内容复制到第二个参数文件中2

    (2)外部命令:

    非内部命令的命令为外部命令,拟设计shell 程序可以自动查找并执行外部命令。

    (3)执行脚本文件:

    如果不加参数的时候,则进入命令行输入模式,否则从.sh的脚本文件中批量执行命令。

    (4)系统环境变量

    拟设计可以显示并修改系统的环境变量。

    (5)I/O重定向

    拟设计支持I/O重定向的功能,在输入要执行的命令后,输入‘<’,再接输入重定向到的文件inputfile(从inputfile中读取而非从标准输入中读取);输入‘>’或者‘>>’再接输出重定向到的文件outputfile,myshell就会将命令执行的结果输出到outputfile中而非输出到屏幕上,其中‘>’表示覆盖写,‘>>’表示追加写

3.2.2 算法流程

考虑到实际的bash总是在不停的等待用户的输入,用户输入后回车,然后执行相应的操作后又等待用户的下一次的输入。因此整体上是一个无限循环过程,mian()函数可以用while(1){...}的结构来模拟这个无限循环的过程。每一次循环主要包括如下几步:

  • 首先要先打印命令提示符。 打开Ubuntu自带的终端,可以发现提示符分为三个部分:“用户名@主机名当前目录$”
  • 然后读取用户的输入。考虑到用户的输入有两种情况,一种是标准的输入流,一种是脚本文件。因此可以设定一个指向脚本文件的指针,如果该指针为空,则从标准输入流中读取,否则从参数指定的脚本文件中读取命令。
  • 为了识别输入的命令,需要设计一个命令分割函数。定义分隔符为空格 ,换行符\n,制表符\t。命令拆分后,可以得到各自的命令参数和数目,并可以据此判断其是否为特殊命令,如管道命令、重定向命令。
  • 然后判断命令是否合法,如果不合法则打印相关的错误信息,如果合法则比对是哪一条内置命令,并执行该命令。

整体的算法流程图如下所示:
在这里插入图片描述

3.3 详细设计

3.3.1 内建命令
help

实现函数:Help()

实现原理:打印用户手册

void Help()
{
  printf("*********************************************************************")
  printf("欢迎查看zyw_shell的用户手册 (^_^)/ \n");
  printf("Author:ZhangYuwei	| Date:2022/3/10  | Email:19291255@bjtu.edu.cn \n\n");
  printf("\n\n(1)内建命令的使用\n"); 
  printf("+-------------------------------------------------------------------+\n\n");
  printf("【cd】\n");
  printf("解释:  无参数则显示当前目录,有参数则改变当前目录为参数内容\n");
  printf("示例:  cd 或 cd /home/zyw \n\n");
  printf("【clr】\n");
  printf("解释:  清空当前屏幕内容,无参数\n");
  printf("示例:  cd 或 cd /home/zyw \n\n");
  printf("【echo】\n");
  printf("解释:  无参数则显示空内容,有参数则显示参数内容\n");
  printf("示例:  echo 或者 echo hello\n\n");  
  printf("【exec】\n");
  printf("解释:  使用参数代表的命令替换当前进程\n");
  printf("示例:  exec ls\n\n");
  printf("【exit】\n");
  printf("解释:  退出当前进程,无参数\n");
  printf("示例:  exit\n\n");
  printf("【environ】\n");
  printf("解释:  显示所有的环境变量,无参数\n");
  printf("示例:  environ\n\n");
  printf("【cpoy】\n");
  printf("解释:  文件复制,要有两个参数\n");
  printf("示例:  copy a.txt b.txt\n\n");
  printf("【help】\n");
  printf("解释:  显示用户手册,无参数\n");
  printf("示例:  help\n\n");
  printf("【pwd】\n");
  printf("解释:  显示当前路径,无参数\n");
  printf("示例:  pwd\n\n");
  printf("【quit】\n");
  printf("解释:  退出zyw_shell,无参数\n");
  printf("示例:  quit\n\n");  
  printf("【touch】\n");
  printf("解释:  创建文件,有一个参数\n");
  printf("示例:  touch a.txt\n\n");    
  printf("【set】\n");
  printf("解释:  无参数时,显示所有环境变量;有2个参数时,设置第1个参数代表的环境变量的值为第2个参数\n");
  printf("示例:  set 或 set USER Zhang\n\n");  
  printf("【time】\n");
  printf("解释:  显示当前时间,无参数\n");
  printf("示例:  time\n\n");
  printf("【unset】\n");
  printf("解释:  将参数所指的环境变量的值取消,只有一个参数\n");
  printf("示例:  unset USER\n\n");
 

  printf("\n\n(2)外部命令\n"); 
  printf("+-------------------------------------------------------------------+\n\n");
  printf("解释: 除了内建命令之外,zyw_shell还能够自动查找并执行外部命令\n");
  printf("示例: ls -l 或 vi hadoop.txt\n\n");
 
  printf("\n\n(3)脚本文件的执行\n"); 
  printf("+-------------------------------------------------------------------+\n\n");
  printf("解释: zyw_shell能够从脚本文件中提取命令行输入,在调用zyw_shell时,如果不加参数则进入命令行输入模式,如果加上一个脚本文件的参数,则会从参数代表的文件中提取命令并执行\n\n");
  printf("示例: zyw_shell test.sh\n\n");
    
  printf("\n\n(4)系统环境\n"); 
  printf("+-------------------------------------------------------------------+\n\n");
  printf("解释: zyw_shell可以显示并修改系统的环境变量。\n\n");
    
  printf("\n\n(5)I/O重定向\n"); 
  printf("+-------------------------------------------------------------------+\n\n");
  printf("解释: zyw_shell支持I/O重定向的功能,在输入要执行的命令后,输入‘<’,再接输入重定向到的文件inputfile(从inputfile中读取而非从标准输入中读取);输入‘>’或者‘>>’再接输出重定向到的文件outputfile,zyw_shell就会将命令执行的结果输出到outputfile中而非输出到屏幕上,其中‘>’表示覆盖写,‘>>’表示追加写\n\n");
  printf("示例: wc < hadoop1.txt >> hadoop2.txt\n\n");
 printf("*****************************END************************************\n")
}
environ

实现函数:Environ()

函数功能:打印所有的环境变量

实现原理:遍历指针数组environ并打印。每个数组元素是指向环境变量字符串的指针。

void Environ()
{
  extern char ** environ;
  for(int i=0;environ[i]!=NULL;i++)
    printf("%s\n",environ[i]);  
}

实现函数:Add_environ

函数功能:添加环境变量

实现原理:直接调用putenv()函数即可添加

void Add_environ(char* str)
{
  putenv(str);   // 添加环境变量
}
cd

实现函数:Cd()

函数功能:切换目录

实现原理:通过getcwd()函数获取当前目录并存储到old_path中,通过chdir()函数实现目录切换

void Cd(int num, int id)
{
  char old_path[MAX_PATH_LENGTH];
  getcwd(old_path,MAX_PATH_LENGTH);
  if(num == 1){ //无参数
    printf("%s\n",old_path);   //输出当前目录
  }
  else if(num > 2) // 参数个数大于2
    fprintf(stderr, RED "[zyw_shell] Error: 输入参数过多!\n");
  else{//一个参数
    //如果切换到该参数所指的目录成功,则修改PWD为新的当前目录
    if(!chdir(commands[id+1]))
      setenv("PWD",commands[id+1],1);
    //否则说明路径不存在
    else
      fprintf(stderr, RED "[zyw_shell] Error: 找不到名为%s的路径!\n",commands[id+1]); 
  } 
}
clr

实现函数:Clr()

函数功能:清屏

实现原理:直接通过printf实现清屏,其中参数clear为宏定义。

#define CLEAR "\e[1;1H\e[2J"
void Clr()
{
  printf(CLEAR);
}
copy

实现函数:Copy()

函数功能:将参数一指示的文件复制到参数二指示的文件中

实现原理:利用c语言中文件的读写操作

void Copy(int num,int id) {
	int from_fd,to_fd;
	int bytes_read,bytes_write;
	char buffer[MAX_INBUF_SIZE];
	char*ptr;
    if(num==3){
        //打开原始文件
        if((from_fd=open(commands[id+1],O_RDONLY))==-1)
        {
            fprintf(stderr, RED "[zyw_shell] Error:无法打开文件%s!\n",commands[id+1]);
            return;
        }
        printf("正在打开 %s...\n",commands[id+1]);
        //创建目标文件
        if((to_fd=open(commands[id+2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
        {
            fprintf(stderr, RED "[zyw_shell] Error:无法打开文件%s!\n",commands[id+2]);
            return;
        }
        printf("正在复制到 %s...\n",commands[id+2]);
        //classical codes of copy file
        while(bytes_read=read(from_fd,buffer,MAX_INBUF_SIZE))
        {
            //error
            if(bytes_read==-1){
              break;
            }
            else if (bytes_read>0)
            {
                ptr=buffer;
                while(bytes_write=write(to_fd,ptr,bytes_read))
                {
                    //error
                    if(bytes_write==-1) break;
                    //all read btyes
                    else if(bytes_write==bytes_read) break;
                    //continue writing
                    else if(bytes_write>0)
                    {
                        ptr+=bytes_write;
                        bytes_read-=bytes_write;
                    }
                }
                //erro when write
                if(bytes_write==-1) break;
            }
        }
        close(from_fd);
        close(to_fd);
        printf(YELLOW "拷贝成功!\n");
    }
    else{
        fprintf(stderr, RED "[zyw_shell] Error: 输入参数只能有两个!\n");
    }
}
pwd

实现函数:Pwd()

函数功能:显示当前目录

实现原理:通过getcwd()函数获取当前目录并存储到now_path中, 输出now_path的内容到屏幕

void Pwd()
{
  char now_path[MAX_PATH_LENGTH];
  getcwd(now_path,MAX_PATH_LENGTH);
  printf("%s\n",now_path);
}
exit

实现函数:Exit()

函数功能:正常退出当前进程

实现原理:直接调用stdlib.h库中的exit()函数即可

void Exit()
{
  exit(0);
}
time

实现函数:Time()

函数功能:显示当前时间

实现原理:time()函数返回从公元1979年1月1日的UTC时间从0时0分0秒起到现在经过的秒数。locattime()函数将参数所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法,然后将此结果由结构体tm返回。

void Time()
{
  //定义类型为time_t的变量nowtime
  time_t nowtime;
  //定义指向tm结构的指针t
  struct tm *t;
  char* xingqi[] = {"星期天","星期一","星期二","星期三","星期四","星期五","星期六"};
  time(&nowtime);
  t = localtime(&nowtime);
  //输出当前时间,格式为:年/月/日 星期X 时:分:秒
  printf("%d年%d月%d日 %s %02d:%02d:%02d\n",(1900+t->tm_year),(1+t->tm_mon),t->tm_mday,xingqi[t->tm_wday],t->tm_hour,t->tm_min,t->tm_sec);
}
touch

实现函数:Touch()

函数功能:创建一个新文件

实现原理:利用c语言中的文件写操作进行创建。

void Touch(int num, int id){
  if(num==2){
    FILE *file=fopen(commands[id+1],"w");
    fclose(file);
    printf(YELLOW "成功创建文件%s\n",commands[id+1]);
  }
  else{
    fprintf(stderr, RED "[zyw_shell] Error: 输入参数只能有一个!\n");
  }
}
echo

实现函数:echo()

函数功能:在屏幕上显示内容并换行

实现原理:直接循环输出保存的参数,最后打印一个换行符即可

void Echo(int num, int id)
{
  for(int i = 1;i < num;i++)
    printf("%s ",commands[id + i]);
  printf("\n");
}

exec

实现函数:Exec()

函数功能:使用指定命令替换当前的shell

实现原理:不创建子进程,直接调用execvp()函数执行命令替换当前shell,如果execvp()返回值不为0,说明没有找到命令,输出错误信息

void Exec(int num, int id)
{
  if(num == 1)
    fprintf(stderr, RED "[zyw_shell] Error: 该命令需要一个参数!\n"); 
  else{
    char ** commands_for_exec = &(commands[id + 1]); //commands[id + 1] 指向待执行的命令
    if(execvp(commands[id + 1],commands_for_exec)!=0)
      fprintf(stderr, RED "[zyw_shell] Error: 无法找到这个命令!\n"); 
  }
}

set

实现函数:Set()

函数功能:设置环境变量的值,没有参数则列出所有环境变量

实现原理:没有输入参数,则执行Environ_func()函数列出所有环境变量的值;输入了两个参数,第一个为变量名,第二个为变量值,如果没有找到输入的环境变量,则输出错误信息,找到了则设置该环境变量的值 。

void Set(int num, int id)
{
  if(num == 1)
    Environ();
  else if(num == 3){
    char* env = getenv(commands[id + 1]);
    if(!env)
      fprintf(stderr, RED "[zyw_shell] Error: 没有该环境变量!\n"); 
    else
      setenv(commands[id + 1],commands[id + 2],1);
  }
  //参数数量不对,则输出错误信息
  else
    fprintf(stderr, RED "[zyw_shell] Error: 参数数量只能是0个或者两个!\n"); 
}
void Environ() // 显示环境变量
{ 
  extern char ** environ;
  for(int i=0;environ[i]!=NULL;i++)
    printf("%s\n",environ[i]);  
}

void Add_environ(char* str)
{
  putenv(str);   // 添加环境变量
}
unset

实现函数:Unset()

函数功能:删除环境变量

实现原理:输入1个参数为将要取消的环境变量,如果没有找到输入的环境变量,则输出错误信息,找到了则设置该环境变量的值为空字符串。否则参数数量不对,则输出错误信息

void Unset(int num, int id)
{
  if(num == 2){
    char* env = getenv(commands[id + 1]);
    if(!env)
      fprintf(stderr, RED "[zyw_shell] Error: 找不到环境变量!\n"); 
    else
      setenv(commands[id + 1],"",1);
  }
  else
    fprintf(stderr, RED "[zyw_shell] Error: 参数数量只能为1个!\n"); 
}
3.1.2 主程序
显示命令提示符

实现函数:DisplayInfo()

函数功能:该函数用来显示命令提示符,包含当前路径,用户名和主机名

实现原理:通过getccwd()获得当前路径存储在path中, getuid()函数获取用户id; getpwuid()函数根据用户id获取当前路径,保存到pwd中; 通过gethostname()获得当前主机名保存到hostname中

void DisplayInfo()
{
  char path[MAX_PATH_LENGTH]; //当前路径
  char usrname[MAX_USRNAME_LENGTH];//用户名
  char hostname[MAX_HOSTNAME_LENGTH];//主机名
  getcwd(path,MAX_PATH_LENGTH);
  struct passwd *pwd; //声明结构变量pwd来保存含有用户名信息的passwd结构
  pwd = getpwuid(getuid());
  strcpy(usrname,pwd->pw_name); 
  gethostname(hostname,MAX_HOSTNAME_LENGTH);
  printf(YELLOW "[zyw_shell]%s@%s", usrname,hostname);
  printf(WHITE ":");
  printf(CYAN "%s", path);
  printf(WHITE "$ ");
}
读取用户输入

实现函数:Read_command()

函数功能:读取用户输入的命令以及参数并存储到全局变量inbuf中

实现原理:首先设置一个脚本指针script_fp,如果传入的参数script_fp为NULL,则从标准输入流读取MAX_INBUF_SIZE-1个字符到inbuf中。否则从参数所指的脚本文件中读取。

void Read_command(FILE* script_fp)
{
  if(!script_fp)
    fgets(inbuf,MAX_INBUF_SIZE-1,stdin);
  else{
    if(fgets(inbuf,MAX_INBUF_SIZE-1,script_fp) == NULL){
      fclose(script_fp);
      exit(0);
    }      
  }
}
命令分解

实现函数:Split_command()

函数功能:分割读取的输入并存储到全局变量commands中

实现原理:定义分隔符为空格,换行符和制表符,利用strtok()函数将inbuf以分隔符delim进行分割,第一次分割的结果存放在temp中。如果temp为NULL,说明用户没有输入或者输入均为分隔符,返回1,temp不为NULL,则将temp赋值给commands[0]。在command_count不超过MAX_COMMAND_NUM,且继续分割的结果字符串不为NULL时,将command_count每次递增1。用两个整数needcpy_infileneedcpy_outfile分别指示是否需要拷贝, 需要输入重定向和输出重定向文件的路径。如果needcpy_infile为1,则将当前分割结果复制到infile_path中并重新设置needcpy_infile为0,如果needcpy_outfile为1,则将当前分割结果复制到outfile_path中并重新设置needcpy_outfile为0 。如果当前分割结果为"<“,说明接下来将要读入输入重定向的文件,设置needcpy_infile和Is_in_redirect为1;如果当前分割结果为”>“,说明接下来将要读入输出重定向(覆盖写)的文件,设置needcpy_outfile和Is_out_redirect_cov为1 。如果当前分割结果为”>>",说明接下来将要读入输出重定向(追加写)的文件,设置needcpy_outfile和Is_out_redirect_app为1

int Split_command()
{ 
  for(int i=0; i<MAX_COMMAND_NUM; i++)
    commands[i] = NULL;//初始化commands的每一项为NULL
  command_num = 0;
  int needcpy_infile = 0;  
  int needcpy_outfile = 0;  
  char* temp;//指向第一个分割的值  
  char* delim = " \n\t";
  temp = strtok(inbuf,delim);
  if(!temp)
    return 1;
  commands[0] = temp;
  command_num++;
  while(command_num < MAX_COMMAND_NUM && (commands[command_num] = strtok(NULL,delim)) ){  
    if(needcpy_infile){
      strcpy(infile_path,commands[command_num]);
      needcpy_infile = 0;
      continue;
    }
    if(needcpy_outfile){
      strcpy(outfile_path,commands[command_num]);
      needcpy_outfile = 0;
      continue;
    }   
    if(strcmp(commands[command_num],"<") == 0){
      needcpy_infile = 1;
      Is_in_redirect = 1;
      continue;
    }
    if(strcmp(commands[command_num],">") == 0){
      needcpy_outfile = 1;
      Is_out_redirect_cov = 1;
      continue;
    }
     
    if(strcmp(commands[command_num],">>") == 0){
      needcpy_outfile = 1;
      Is_out_redirect_app = 1;
      continue;
    }
    command_num++; //计数
  }
  if(command_num >= MAX_COMMAND_NUM) //参数过多,返回2
    return 2;
  return 0;// 正常结束返回0
}
选择并执行内建命令

实现函数:My_command()

函数功能:选择并执行myshell的内建命令

实现原理:根据不同的命令选择相应的函数执行内建命令

int My_command(int count, int index)
{
  if(strcmp(commands[index],"clr") == 0)
    Clr();  
  else if(strcmp(commands[index],"copy") == 0)
    Copy();  
  else if(strcmp(commands[index],"environ") == 0)
    Environ();  
  else if(strcmp(commands[index],"pwd") == 0)
    Pwd();  
  else if(strcmp(commands[index],"exit") == 0)
    Exit();  
  else if(strcmp(commands[index],"time") == 0)
    Time();   
  else if(strcmp(commands[index],"touch") == 0)
    Touch();     
  else if(strcmp(commands[index],"cd") == 0)
    Cd(count,index);   
  else if(strcmp(commands[index],"echo") == 0)    
    Echo(count,index); 
  else if(strcmp(commands[index],"exec") == 0)   
    Exec(count,index); 
  else if(strcmp(commands[index],"set") == 0)     
    Set(count,index);     
  else if(strcmp(commands[index],"unset") == 0)    
    Unset(count,index);  
  else if(strcmp(commands[index],"help") == 0)
    Help();
  else
    return 1;
  return 0;
}
执行命令

实现函数:Run_command()

函数功能:执行所有命令的接口,包括内建命令和外部命令。

实现原理:首先判断命令是否包含输入或者输出重定向,以及判断是内部命令还是外部命令。然后根据命令所属的种类执行不同的操作。

void Run_command()
{
  //分别备份stdin和stdout的文件描述符到stdinFd和stdoutFd中
  int stdinFd = dup(fileno(stdin));
  int stdoutFd = dup(fileno(stdout));  
  //命令中包含了输入重定向
  if(Is_in_redirect){
    int fileFd = 0;
    //以只读方式打开infile_path
    fileFd = open(infile_path, O_RDONLY, 0666);
    //将标准输入重定向到infile_path,如果失败则输出错误信息
    if(dup2(fileFd, fileno(stdin)) == -1)
      fprintf(stderr,RED "[zyw_shell] Error: dup2() 失败!\n");
    //关闭文件
    close(fileFd);
  }
  
  //命令中包含了输出重定向(覆盖写)
  if(Is_out_redirect_cov){
    int fileFd = 0;
    //以读写,覆盖写方式打开outfile_path
    fileFd = open(outfile_path, O_RDWR | O_CREAT | O_TRUNC, 0666);
    //将标准输出重定向到outfile_path,如果失败则输出错误信息
    if(dup2(fileFd, fileno(stdout)) == -1)
      fprintf(stderr,RED "[zyw_shell] Error: dup2() 失败!\n");
    //关闭文件  
    close(fileFd);
  }
  
  //命令中包含了输出重定向(追加写)
  if(Is_out_redirect_app){
    int fileFd = 0;
    //以读写,追加写方式打开outfile_path
    fileFd = open(outfile_path, O_RDWR | O_CREAT | O_APPEND, 0666);
    //将标准输出重定向到outfile_path,如果失败则输出错误信息
    if(dup2(fileFd, fileno(stdout)) == -1)
      fprintf(stderr,RED "[zyw_shell] Error: dup2() is 失败!\n");
    //关闭文件    
    close(fileFd);
  }
  
  //调用My_command()函数执行命令,若该函数返回值为1,说明为外部命令,需要继续处理
  if(My_command(command_num,0) == 1){
        //调用execvp()执行外部命令,如果失败则输出错误信息       
        if(execvp(commands[0],commands)!=0)
          fprintf(stderr, RED "[zyw_shell] Error: 无法找到命令 \"%s\"!\n", commands[0]);
        //正常退出子进程
        exit(EXIT_SUCCESS);
      }    
  }
  
  //如果Is_in_redirect为1,说明之前进行了输入重定向,需要复原
  if(Is_in_redirect){
    //复原标准输入,失败则输出错误信息
    if(dup2(stdinFd, fileno(stdin)) == -1)
      fprintf(stderr,RED "[zyw_shell] Error: dup2() 失败!\n");
    //关闭文件
    close(stdinFd);
  }  
  
  //如果Is_out_redirect_cov或Is_out_redirect_app为1,说明之前进行了输出重定向,需要复原
  if(Is_out_redirect_cov || Is_out_redirect_app){
   //复原标准输出,失败则输出错误信息
    if(dup2(stdoutFd, fileno(stdout)) == -1)
      fprintf(stderr,RED "[zyw_shell] Error: dup2() 失败\n");
    //关闭文件
    close(stdoutFd);
  }  
}

main函数
int main(int argc, char **argv)
{
  int script_flag = 0;  //script_flag用来指示脚本文件是否已经被打开
  FILE* fp;
  setenv("SHELL","/home/zyw/myshell_zyw",1);
  while(1){
    Init();// 初始化
    //没有参数传入,则调用Display_prompt()函数显示命令提示符
    if(argc == 1)
      DisplayInfo();
    //没有参数传入时将NULL作为Get_command()函数的参数,否则将fp作为Get_command()函数的参数
    if(argc == 1)
      Get_command(NULL);
    else{
      //script_flag为0,即脚本文件还未打开
      if(!script_flag){
        //设置script_flag为1,并打开脚本文件将指针保存到fp中
        script_flag = 1;
        //如果fp为NULL,说明打开失败,则输出错误信息并退出
        if(!(fp = fopen(argv[1], "r"))){
          fprintf(stderr, RED "[myshell] Error: 无法打开脚本文件 \"%s\"!\n", argv[1]);
          exit(1);
        }      
      }
      //script_flag为1,即脚本文件已经打开,调用Get_command(fp)函数读取命令
      else
        Get_command(fp);     
    }
      
    //调用Split_command()函数分割读取的输入并将函数返回值存到flag中
    int flag = Split_command();
    //如果flag的值为1,说明用户没有输入内容或者输入均为分隔符,直接continue
    if(flag == 1)
      continue;
    //如果flag的值为2,说明用户输入的参数个数过多,输出错误信息并continue
    else if(flag == 2){
      fprintf(stderr, RED "[myshell] Error: 参数过多!\n");
      continue;
    }
    //命令正常,则调用Run_command()函数解析命令并相应执行
    else
    Run_command();
  }
  return 0;
}
void Get_command(FILE* script_fp)
{
  if(!script_fp)
    fgets(inbuf,MAX_INBUF_SIZE-1,stdin);
  else{
    if(fgets(inbuf,MAX_INBUF_SIZE-1,script_fp) == NULL){
      fclose(script_fp);
      exit(0);
    }      
  }
}

3.4 编译测试

  • 进入shell

    首先输入命令make进行编译链接,然后输入./myshell 切换到自制的shell。

在这里插入图片描述

  • help命令

    输入help命令可以进入用户手册,在首行可以显示个人信息,以证明是本人完成。
    在这里插入图片描述

  • environ

    输入environ命令后可以显示系统的环境变量
    在这里插入图片描述

    可以和ubuntu 自带的bash shell 对比,结果除了第一行的SHELL不一致外,其余的都相同。

在这里插入图片描述

  • cd

    测试用例1:输入cd+指定目录时,可以正常的切换路径

    测试用例2:输入cd ../成功的退回上一级目录

    测试用例3:输入cd xx, 提示错误找不到该路径

    测试用例4:输入cd x y, 提示输入的参数过多

在这里插入图片描述

  • clr

    执行clr前的屏幕:
    在这里插入图片描述

    按下enter键后,成功的实现了清屏功能。
    在这里插入图片描述

  • copy

    测试用例1: copy a.txt b.txt ,成功的将a文件中的内容写入b文件中

在这里插入图片描述

测试用例2:copy c.txt b.txt 报错无法打开文件c.txt

测试用例3:copy a.txt 报错输入参数只能有两个

在这里插入图片描述

  • pwd

    输入pwd 后可以正常的打印当前的目录

在这里插入图片描述

  • exit

    输入命令exit后退出自制的shell

在这里插入图片描述

  • time

    输入time 可以正常的显示年月日,星期和时间。与电脑的系统时间比对一致。

在这里插入图片描述

  • touch

    测试用例1:touch a.txt,成功创建文件a.txt

    测试用例2:touch b.txt s 报错输入的参数只能有一个!

    测试用例3:touch 报错输入的参数只能有一个!

在这里插入图片描述

  • echo

    测试用例1:输入echo Hello, my name is zyw后,可以成功的显示Hello, my name is zyw

    测试用例2:输入echo 后,只显示一个空行。
    在这里插入图片描述

  • exec

    输入exec ls, 可以看到执行完ls命令后成功的退出自制的shell。

在这里插入图片描述

  • set

    测试用例1:输入set后可以显示所有的环境变量

在这里插入图片描述

测试用例2: 输入 set SHELL /home/shell 成功的将SHELL的环境变量设为了/home/shell

在这里插入图片描述

测试用例3:输入set xx,报错参数的个数只能是0个或者两个

测试用例4:输入set xyz /home ,报错没有该环境变量

在这里插入图片描述

  • unset

    输入unset SHELL后,再次输入set命令查看环境变量,发现SHELL被成功的置为空。

在这里插入图片描述

  • 外部命令

    测试用例1:输入外部命令ls -al

    测试用例2:输入外部命令ps

    在这里插入图片描述

    测试用例3:输入外部命令users

    测试用例4:输入外部命令who

    测试用例5:输入外部命令ping www.baidu.com
    在这里插入图片描述

  • 执行脚本

    首先输入cat testshell.sh 查看该脚本文件

    然后输入./myshell testshell.sh 成功的执行了该脚本文件。

在这里插入图片描述

  • I/O重定向

    测试用例1:输出重定向>,当没有输出文件时,会创建一个文件,并将输出的内容写入到该文件中。

在这里插入图片描述

测试用例2:输出重定向>,当有该文件时,会将输出的内容覆盖写入到该文件中

在这里插入图片描述

测试用例3:输出重定向>> 将输出的内容追加写入到该文件中。

在这里插入图片描述

测试用例4:输入重定向< 将in.txt的内容输出到list.txt,并覆盖其原有的内容。

在这里插入图片描述

  • 23
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 51
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 51
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyw2002

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值