多进程|sh3.c 实现管道

1.题目要求

实现shell程序,要求在第2版的基础上,添加如下功能

  • 实现管道

  • 只要求连接两个命令,不要求连接多个命令

  • 不要求同时处理管道和重定向

    # 执行sh3
    $ ./sh3
    
    # 执行命令cat和wc,使用管道连接cat和wc
    > cat /etc/passwd | wc -l
    
  • 考虑如何实现管道和文件重定向,暂不做强制要求

    $ cat input.txt
    3
    2
    1
    3
    2
    1
    $ cat <input.txt | sort | uniq | cat >output.txt
    $ cat output.txt
    1
    2
    3
    

2.解决思路

​ 本题实现参考sh3的实现提示文档 https://www.linuxmooc.com/teach/os/sh3-hint.pdf 。首先定义command数据结构:

struct command {
  int argc;
  char *argv[MAX_ARGC];
  char *input;    //用于重定向输入
  char *output;   //用于重定向输出
};

并定义全局变量:

int command_count;
struct command commands[MAX_COMMANDS];

command_count用于对命令计数,若值为1则该命令为非管道命令,值>1则为管道命令。实现处理字符串函数分割用户键入的完整命令并存储在结构体数组commands中,在函数void parse_commands(char *line)中用 '|' 作为分隔符切割 line, 将多条命令按 | 分割,该函数调用了子函数void parse_command(char *line, int index),用 ' ' 作为分隔符切割 line, 将一条命令按空格分割。并且实现void commands_dump()函数用于验证字符串处理是否正确,经过测试发现不能使用strtok,而应该使用strtok_r,否则存储结果的结构体数组只有第一条命令。

​ 接下来需要重构mysys函数,调用函数进行字符串处理之后,需要对command_count的值做判断,若为0直接返回;若为1 表示非管道命令,首先判断是否为内置命令,若是调用相应的函数完成处理后返回;否则创建进程,调用子函数exec_simple()装入程序执行;若为2表示管道命令,创建进程,调用子函数exec_pipe()执行管道命令,在函数中先创建管道, 再创建进程,子进程将标准输出定向到管道的写端,父进程将标准输入定向到管道的读端。

3.代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 256
#define MAX_ARGC 16
#define MAX_COMMANDS 16
#define MAX_SIZE 256

struct command {
  int argc;
  char *argv[MAX_ARGC];
  char *input;    //用于重定向输入
  char *output;   //用于重定向输出
};
int command_count;
struct command commands[MAX_COMMANDS];


void panic(char *message) {
  perror(message);
  exit(EXIT_FAILURE);
}

// 用 ' ' 作为分隔符切割 line, 将一条命令按空格分割
void parse_command(char *line, int index) {
  char *field = malloc(sizeof(line));
  strcpy(field, line);
  char *delim = " \n";
  commands[index].argc = 0;
  char *ptr = NULL;

  for(int i = 0; i < MAX_ARGC; i++) 
    commands[index].argv[i] = NULL;
  commands[index].input = NULL;
  commands[index].output = NULL;

  char *word = strtok_r(field, delim, &ptr);
  while(word) {
    if(strchr(word, '>'))
      commands[index].output = word + 1;
    else if(strchr(word, '<')) 
      commands[index].input = word + 1;
    else
      commands[index].argv[commands[index].argc++] = word;
    word = strtok_r(NULL, delim, &ptr);
  }
}

// 用 '|' 作为分隔符切割 line, 将多条命令按 | 分割
void parse_commands(char *line) {
  // char *fields = malloc(sizeof(line));
  char *fields = (char *)malloc(MAX_SIZE);
  strcpy(fields, line);
  char *delim = "|\n";
  char *ptr = NULL;

  command_count = 0;
  char *field = strtok_r(fields, delim, &ptr);
  while(field) {
    parse_command(field, command_count);
    command_count++;
    field = strtok_r(NULL, delim, &ptr);
  }
}

// 打印 command 的 argc 和 argv, 验证字符串处理是否正确
void command_dump(struct command command) {
  printf("   argc = %d\n", command.argc);
  printf("   argv = {");
  for(int i = 0; i < command.argc; i++) {
    printf("\"%s\"", command.argv[i]);
    if(i < command.argc - 1)
      printf(", ");
  }
  printf("}\n");
}

// 打印 commands 中的每一个 command, 验证字符串处理是否正确
void commands_dump() {
  printf("\n-------------------parse commands start-------------------\n");
  for(int i = 0; i < command_count; i++) {
    printf("\ncommand[%d]\n", i);
    command_dump(commands[i]);
  }
  printf("\n-------------------parse commands end---------------------\n\n");
}


// 处理内置命令cd、pwd、exit
void handle_built_in_command(int index) {
  if(strcmp(commands[index].argv[0], "cd") == 0) {
    int error = chdir(commands[index].argv[1]);
    if(error < 0) 
      perror("cd");
    else {
      char *path = getcwd(NULL, 0);
      printf("current working directory: %s\n", path);
      free(path);
    }
  }
  else if(strcmp(commands[index].argv[0], "pwd") == 0) {
    char *path = getcwd(NULL, 0);
    printf("current working directory: %s\n", path);
    free(path);
  }
  else if(strcmp(commands[index].argv[0], "exit") == 0) {
    exit(0);
  }
}

// 处理文件重定向
void handle_redirect(int index) {
  if(commands[index].output) {
    int fd = open(commands[index].output, O_CREAT | O_RDWR);
    if(fd < 0) 
      panic("open");
    dup2(fd, 1);
    close(fd);
  }
  if(commands[index].input) {
    int fd = open(commands[index].input, O_CREAT | O_RDWR);
    if(fd < 0) 
      panic("open");
    dup2(fd, 0);
    close(fd);
  }
}

// 执行非管道命令
void exec_simple(int index) {
  // 处理需要文件重定向的情况
  if(commands[index].input || commands[index].output) {
    handle_redirect(index);
  }
  // 装入程序执行
  int error = execvp(commands[index].argv[0], commands[index].argv);
    if(error < 0)
  perror("execvp");
}

// 执行管道命令
void exec_pipe() {
  int fd[2];
  int pid;

  pipe(fd);         // 先创建管道, 再创建进程
  pid = fork();
  if(pid == 0) {
    dup2(fd[1], 1); // 标准输出定向到管道的写端
    close(fd[0]);
    close(fd[1]);
    exec_simple(0);
  }
  wait(NULL);
  dup2(fd[0], 0);   // 标准输入定向到管道的读端
  close(fd[0]);
  close(fd[1]);
  exec_simple(1);
}


void mysys(char *command) {
  parse_commands(command);
  // commands_dump();

  if(command_count == 0)
    return ;
  else if(command_count == 1) {
    // 单独处理内置命令cd、pwd、exit
    if(strcmp(commands[0].argv[0], "cd") == 0 || strcmp(commands[0].argv[0], "pwd") == 0 || strcmp(commands[0].argv[0], "exit") == 0) {
      handle_built_in_command(0);
      return ;
    }
    pid_t pid = fork();
    if(pid == 0) 
      exec_simple(0);
    wait(NULL);
  }
  else if(command_count == 2) {
    pid_t pid = fork();
    if(pid == 0)
      exec_pipe();
    wait(NULL);
  }
}


int main() {
  char command[SIZE];
  int count;

  while(1) {
    write(1, ">", sizeof(">"));
    count = read(0, command, sizeof(command));
    command[count] = 0;
    mysys(command);
  }
  return 0;
}

4.运行结果

保留commands_dump()函数验证字符串处理是否正确:

$ gcc sh3.c -o sh3
$ ./sh3 
>cat /etc/passwd | wc -l

-------------------parse commands start-------------------

command[0]
   argc = 2
   argv = {"cat", "/etc/passwd"}

command[1]
   argc = 2
   argv = {"wc", "-l"}

-------------------parse commands end---------------------

43
>exit

-------------------parse commands start-------------------

command[0]
   argc = 1
   argv = {"exit"}

-------------------parse commands end---------------------

注释commands_dump()之后:

$ gcc sh3.c -o sh3
$ ./sh3 
>cat /etc/passwd | wc -l
43
>exit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值