重定向

即通过重定向操作符,使得输入输出位置发生更改的功能,称为重定向;

实现之前

为了简化实现过程,我们采用之前所完成的自制shell来实现重定向;自制shell在此 下面分别介绍一下三个常见的重定向符号:

输入重定向(<

<:用于将文件的内容作为命令的标准输入。

command < filename
  • 功能:将filename的内容作为command的标准输入来处理。

输出重定向(>

>:用于将命令的标准输出重定向到文件中,会覆盖目标文件中的原有内容。

command > filename
  • 功能:将command的标准输出写入filename中;如果文件已存在,则会覆盖原有内容;如果文件不存在,则创建该文件。

追加重定向(>>

>>:用于将命令的标准输出重定向到文件中,但它会追加输出到文件的末尾,不会覆盖原有内容。

command >> filename
  • 功能:将command的标准输出追加到filename的末尾;如果文件不存在,则会创建该文件。

具体实现

确定处理状态

首先考虑命令的变化:当进行重定向时,命令行会发生变化,比如

ls > test.txt

可以看到,这次我们不仅需要对指令进行识别,还需要对重定向的符号进行判断,以确定使用什么重定向方式:

// 简单的框架,演示如何读取重定向符号
void checkCommand(char* command){ 
	char* start = command;
	char* end = command + strlen(command);
    // 构建一个循环,用于找出重定向符号
	while(start != end){
        // 可能是输出或追加重定向
 		if(*start == '>'){
            start++;
            if(*start == '>'){
			
    		}
        }
    	// 输入重定向
  		else if(*start == '<'){
			
    	}
    }
}

接下来我们采用标志位的方式,对所需要进行的重定向种类进行标记:

// 不做处理
#define NONE_REDIR 0
// 输入重定向 
#define INPUT_REDIR 1
// 输出重定向
#define OUTPUT_REDIR 2
// 追加重定向
#define APPEND_REDIR 3

最后,我们根据标记进行集中处理; 下面,我们对上述函数进行进一步完善,以提取出可供重定向的信息:

void checkCommand(char* command){
  redir_file = NULL;
  redir_type = NONE_REDIR;

  assert(command);
  char* start = command;
  char* end = command + strlen(command);
  while(start != end){
    umask(0);

    if(*start == '<'){			// 输入重定向
      *start = '\0';
      start++;
      skip_space(start);
      redir_file = start;		// 设置读取位置
      redir_type = INPUT_REDIR;		// 设置处理状态
      break;
    }


    else if(*start == '>'){		// 输出重定向 or 追加重定向 ?
      *start = '\0';
      start++;
      if(*start == '>'){		// 追加重定向
        redir_type = APPEND_REDIR;
        ++start;
        skip_space(start);
        redir_file = start;
        break;
      }
     
      skip_space(start);
      redir_file = start;
      redir_type = OUTPUT_REDIR;
      break;
    }

    else{
      ++start;
    }
  }
}

这里添加了一个skip_space函数,其目的是为了防止用户输出时包含的空格影响读取工作,其具体实现采用了宏函数的编写原则,只是为了减小函数调用的成本,普通函数也可实现:

#define skip_space(str) do{\
  while(isspace(*str)) ++str;\
}while(0) // 需要注意这里的宏函数的编写原则

执行处理

注意,这里的处理同shell程序一样,也是在子进程下启动的(这也是为什么采用自制的shell程序处理的原因,因为自制shell源代码简单且可控,方便我们纠错); 下面是代码实现:

if(id == 0){					// 子进程中启动程序
  int fd = 0; 
  switch(redir_type){
    case INPUT_REDIR:				// 判断处理状态,执行相应处理
      {
        fd = open(redir_file, O_RDONLY);	// 只读
        if(fd < 0){
          perror("wrong open");
          exit(EXIT_FAILURE);
        }
        dup2(fd, STDIN_FILENO);			// 输入的文件标识符
        break;
      }
    case OUTPUT_REDIR:
      {
        fd = open(redir_file, O_WRONLY | O_CREAT);	// 只写 + 不存在创建
        if(fd < 0){
          perror("wrong open");
          exit(EXIT_FAILURE);
        }
        dup2(fd, STDOUT_FILENO);			// 输出的文件标识符
        break;
      }
    case APPEND_REDIR:
      {
        redir_type = APPEND_REDIR;
        fd = open(redir_file, O_WRONLY | O_CREAT);	// 只写 + 不存在创建
        if(fd < 0){
          perror("wrong open");
          exit(EXIT_FAILURE);
        }
        dup2(fd, STDOUT_FILENO);
        break;
      }
    case NONE_REDIR:
      {
        break;
      }
    default:
      {
        perror("default condition");
        // exit(EXIT_FAILURE);
        break;
      }
  }
  close(fd);
  execvp(myargv[0], myargv);
  exit(0);
}
waitpid(id, NULL, 0); 

综上,我们便完成了在自制shell程序中添加重定向功能的任务,具体的完整代码可见我的GitHub主页: X_Nefertarshell代码见Linux_Learning