plan4

文章详细介绍了如何实现一个简单的Shell,包括读取用户输入、处理输入的命令(如管道、重定向、后台运行)、信号屏蔽、记录历史命令等功能,并给出了关键代码片段。该Shell能执行基本的Linux命令操作,但不支持脚本和处理含空格的命令。
摘要由CSDN通过智能技术生成

shell的实现

shell可实现的基本功能

实现 管道 (也就是 |)

实现 输入输出重定向(也就是 < > >>)

实现 后台运行(也就是 & )

实现 cd,要求支持能切换到绝对路径,相对路径和支持 cd -

屏蔽一些信号(如 ctrl + c 不能终止)

记录历史命令

任然存在的不足

1.无法编写shell脚本

2.无法处理不含空格的命令

命令示例

重定向写

echo ABCDEF > ./1.txt

重定向读

python < ./1.py 

重定向读与写

grep "rzj" > 2.txt < 1.txt

管道

cat 1.txt | awk '{print $1}' | sort | uniq -c | sort -r -n | head -n 5

重定向与管道

ls -a -l | grep abc | wc -l > 2.txt
 python < ./1.py | wc -c

后台执行

shell的基本思路与部分代码

主函数部分

在这里插入图片描述

主函数部分应该调用包含有以下功能的函数

1.屏蔽ctrl c ctrl t信号

2.打印提示符

3.命令的读入

4.解析命令行,将每条指令分开

5.判断指令类型

6.记录历史命令

记录历史命令需要用到readline库

gcc myshell.c -o myshell -lreadline     

主函数部分

int main()
{
	read_history(NULL);
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);//屏蔽ctrl c与ctrl t
	while(1){//循环等待用户输入
		Printf();//打印用户信息
		char *command=readline(NULL);//读入命令
		if (commod == NULL) // 当没有命令读入时跳到下一步
    	{
      		printf("\n");
      		continue;
    	}
		char *argv[MAX] = {NULL};
    	arg**=Stork(command);//将指令分割
		isdo(argv);//根据所含有的符号判断需要执行哪种指令
		add_history(commod);//记录历史命令
    	write_history(NULL);
		free(command);//释放
	}
}

重定向

在这里插入图片描述

void mydup(char *argv[])
{
  char *strc[MAX] = {NULL};
  int i = 0;
  while (strcmp(argv[i], ">"))
  {
    strc[i] = argv[i];
    i++;
  }
  int number = i; // 重定向前面参数的个数
  int flag = isdo(argv, number);
  i++;
  // 出现 echo "adcbe" > test.c  这种情况
  int fdout = dup(1);                                         // 让标准输出获取一个新的文件描述符
  int fd = open(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0666); // 只写模式|表示如果指定文件不存在,则创建这个文件|表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
  dup2(fd, 1);
  pid_t pid = fork();
  if (pid < 0)
  {
    perror("fork");
    exit(1);
  }
  else if (pid == 0) // 子进程
  {
    if (flag == 3) // 管道'|'
    {
      callCommandWithPipe(strc, number);
    }
    else
      execvp(strc[0], strc);
  }
  else if (pid > 0)
  {
    if (pass == 1)
    {
      pass = 0;
      printf("%d\n", pid);
      return;
    }
    waitpid(pid, NULL, 0);
  }
  dup2(fdout, 1);
}

管道

在这里插入图片描述

void callCommandWithPipe(char *argv[], int count)
{
  pid_t pid;
  int ret[10];    // 存放每个管道的下标
  int number = 0; // 统计管道个数
  for (int i = 0; i < count; i++)
  {
    if (!strcmp(argv[i], "|"))
    {
      ret[number++] = i;
    }
  }
  int cmd_count = number + 1; // 命令个数
  char *cmd[cmd_count][10];
  for (int i = 0; i < cmd_count; i++) // 将命令以管道分割存放组数组里
  {
    if (i == 0) // 第一个命令
    {
      int n = 0;
      for (int j = 0; j < ret[i]; j++)
      {
        cmd[i][n++] = argv[j];
      }
      cmd[i][n] = NULL;
    }
    else if (i == number) // 最后一个命令
    {
      int n = 0;
      for (int j = ret[i - 1] + 1; j < count; j++)
      {
        cmd[i][n++] = argv[j];
      }
      cmd[i][n] = NULL;
    }
    else
    {
      int n = 0;
      for (int j = ret[i - 1] + 1; j < ret[i]; j++)
      {
        cmd[i][n++] = argv[j];
      }
      cmd[i][n] = NULL;
    }
  }                                // 经过上述操作,我们已经将指令以管道为分隔符分好,下面我们就可以创建管道了
  int fd[number][2];               // 存放管道的描述符
  for (int i = 0; i < number; i++) // 循环创建多个管道
  {
    pipe(fd[i]);
  }
  int i = 0;
  for (i = 0; i < cmd_count; i++) // 父进程循环创建多个并列子进程
  {
    pid = fork();
    if (pid == 0) // 子进程直接退出循环,不参与进程的创建
      break;
  }
  if (pid == 0) // 子进程
  {
    if (number)
    {
      if (i == 0) // 第一个子进程
      {
        dup2(fd[0][1], 1); // 绑定写端`
        close(fd[0][0]);   // 关闭读端
        // 其他进程读写端全部关闭
        for (int j = 1; j < number; j++)
        {
          close(fd[j][1]);
          close(fd[j][0]);
        }
      }
      else if (i == number) // 最后一个进程
      {
        dup2(fd[i - 1][0], 0); // 打开读端
        close(fd[i - 1][1]);   // 关闭写端
                             // 其他进程读写端全部关闭
        for (int j = 0; j < number - 1; j++)
        {
          close(fd[j][1]);
          close(fd[j][0]);
        }
      }
      else // 中间进程
      {
        dup2(fd[i - 1][0], 0); // 前一个管道的读端打开
        close(fd[i - 1][1]);   // 前一个写端关闭
        dup2(fd[i][1], 1);     // 后一个管道的写端打开
        close(fd[i][0]);       // 后一个读端关闭
        // 其他的全部关闭
        for (int j = 0; j < number; j++)
        {
          if (j != i && j != (i - 1))
          {
            close(fd[j][0]);
            close(fd[j][1]);
          }
        }
      }
    }

    execvp(cmd[i][0], cmd[i]); // 执行命令
    perror("execvp");
    exit(1);
  }

组合命令

在这里插入图片描述

根据整理出的规律应该先判断 > 再判断 | 最后判断 <

循环挨个检查命令符号的顺序,在各种操作中再根据命令的规律再不同的操作中再次调用isdo函数得到新的flag决定后续操作的执行

int isdo(char *argv[], int count)
{
  int flag = 10, i;
  if (argv[0] == NULL)
    return 0;
  if (strcmp(argv[0], "cd") == 0)
  {
    flag = 1;
  }
  for (i = 0; i < count; i++)
  {
    if (strcmp(argv[i], ">") == 0)
      flag = 2;
    if (strcmp(argv[i], "|") == 0)
      flag = 3;
    if (strcmp(argv[i], ">>") == 0)
      flag = 4;
    if (strcmp(argv[i], "<") == 0)
      flag = 5;
    if (strcmp(argv[i], "<<") == 0)
      flag = 6;
    if (strcmp(argv[i], "&") == 0)
    {
      pass = 1;
      argv[i] = NULL;
    }
  }
  return flag;
}

完整代码:
https://github.com/AAAwlx/wlx-dm/blob/main/2.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值