13.番外_自定义Shell

13.番外:自定义Shell

一,设计

Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。

设计思路:

  1. 获取命令行

  2. 解析命令行

  3. 建立一个子进程(fork)

  4. 替换子进程(execvp)

  5. 父进程等待子进程的退出(wait)

image

二,代码实现解析

伪代码:

int main()
{
  do_bash_cin负责显示提示符,读取用户输入的命令
  do_bash_parse解析命令,拆分为独立的参数
  do_exec创建子进程并执行程序命令       
}

main函数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#define MAX_CMD 1024
char command[MAX_CMD];

int main(int argc, char *argv[])
{
    while(1) {
        if (do_bash_cin() < 0) // 调用do_face读取用户输入
            continue; // 如果读取失败,继续下一次循环
        do_exec(command); // 执行命令
    }
    return 0;
}

do_bash_cin()

int do_bash_cin()
{
    memset(command, 0x00, MAX_CMD); // 将command数组清零
    printf("minishell$ "); // 打印提示符
    fflush(stdout); // 刷新输出缓冲区
    if (scanf("%[^\n]%*c", command) == 0) { // 从标准输入读取一行命令
        getchar(); // 读取并丢弃一个字符(换行符)
        return -1; // 返回-1表示读取失败
    }
    return 0; // 成功读取命令,返回0
}

具体解析一下关于scanf()这一行:

image

image

do_exec()

int do_exec(char *buff)
{
    char **argv = {NULL}; // 参数数组初始化为NULL
    int pid = fork(); // 创建子进程
    if (pid == 0) 
    { // 子进程
        argv = do_bash_parse(buff); // 解析命令行参数
        if (argv[0] == NULL) {
            exit(-1); // 如果没有命令,退出子进程
        }
        execvp(argv[0], argv); // 执行命令
    } 
    else 
    {
        waitpid(pid, NULL, 0); // 父进程等待子进程结束
    }
    return 0;
}

**char **argv = {NULL};** 这一行代码初始化了一个指向字符指针的指针,并将其设置为 **NULL**

这种初始化方式确保在使用前指针是有效的空指针,从而避免未初始化指针可能带来的错误。

ps:由此可见那么do_bash_parse一定是指向字符指针的指针

do_bash_parse()

char **do_bash_parse(char *buff)
{
    int argc = 0; // 参数计数
    static char *argv[32]; // 存储命令行参数的数组,最大支持32个参数
    char *ptr = buff; // 指向命令字符串的指针
    while(*ptr != '\0') 
    {
        if (!isspace(*ptr)) // 如果当前字符不是空白字符
        { 
            argv[argc++] = ptr; // 将参数的起始地址存储在argv中
            while((!isspace(*ptr)) && (*ptr) != '\0') {
                ptr++; // 跳过参数的所有字符
            }
        } 
        //if结构处理非空白字符
        else 
        {
            while(isspace(*ptr)) {
                *ptr = '\0'; // 将空白字符替换为字符串结束符
                ptr++; // 跳过所有连续的空白字符
            }
        }
    }
    argv[argc] = NULL; // 最后一个参数后置NULL
    return argv; // 返回参数数组
}

解析函数:

Q1:static char *argv[32];为什么使用static呢?

A:如果不使用static,每次调用do_bash_parse函数时,argv数组都会重新创建和初始化,解析的结果无法保留。而我们需要在函数返回后仍然能够访问解析后的参数。

使用static可以确保返回的指针指向的数组在整个程序运行期间是有效的。

Q2:解析逻辑是什么?

A:

image

三,代码实现与效果

代码:这是用easy_shell打印的结果minishell$ cat test.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<ctype.h>
#include<sys/wait.h>
#define MAX_CMD 1024
char command[MAX_CMD];
int do_bash_cin()
{
  memset(command,0x00,MAX_CMD);
  printf("minishell$ ");
  fflush(stdout);
  if(scanf("%[^\n]%*c",command)==0)
  {
    getchar();
    return -1;
  }
  return 0;
}
char **do_bash_parse(char *buff)
{
  int argc=0;
  static char *argv[32];
  char *ptr = buff;
  while(*ptr!='\0')
  {
    if(!isspace(*ptr))
    {
      argv[argc++]=ptr;
      while((!isspace(*ptr))&&(*ptr)!='\0')
      {
        ptr++;
      }
    }
    else
    {
      while(isspace(*ptr))
      {
        *ptr='\0';
        ptr++;
      }
    }
  }
  argv[argc]=NULL;
  return argv;  
}
int do_exec(char *buff)
{
  char **argv={NULL};
  pid_t pid =fork();
  if(pid==0)
  {
    argv=do_bash_parse(buff);
    if(argv[0]==NULL)
    {
      exit(-1);
    }
    execvp(argv[0],argv);
  }
  else{
    waitpid(pid,NULL,0);
  }
  return 0;
}
int main(int argc,char *argv[])
{
  while(1)
  {
    if(do_bash_cin()<0) continue;
    do_exec(command);
  }
  return 0;
}

效果:

image

问题:为什么输入ll不显示结果呢?

在代码中,**execvp** 函数试图执行 **ll** 命令,但是这个命令在标准的命令行环境中可能不存在。**ll** 是一个常用的 Linux/Unix 命令,用于显示目录中的文件列表及其详细信息,但是它通常是 **ls -l** 的别名

四,代码传递展现

  1. 首先,程序提示用户输入命令:

    minishell$

  2. 用户输入命令 ls -al 并按下回车键。

    minishell$ ls -al

  3. do_bash_cin 函数读取用户输入的命令,并存储在 command 数组中。

    command[] = “ls -al\0”

  4. main 函数调用 do_exec 函数,将 command 数组作为参数传递给它。

  5. do_exec 函数创建一个子进程,并在子进程中调用 execvp 函数来执行命令。

  6. 子进程调用 do_bash_parse 函数来解析命令,并得到参数数组 argv

    argv[0] = “ls\0”
    argv[1] = “-al\0”
    argv[2] = NULL

  7. execvp 函数执行 ls 命令,并将参数数组传递给它。此时,ls 命令被执行,显示当前目录下所有文件及其详细信息。

  8. 子进程执行完毕,返回到父进程,父进程调用 waitpid 函数等待子进程退出。

Q:请依据此详细阐述一下do_bash_parse()的while过程

image

A:

image

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值