【Linux】重定向dup

文章详细介绍了文件描述符的工作规则,特别是如何通过关闭和重新分配文件描述符实现输出、输入和追加重定向。通过示例代码解释了printf和fprintf如何根据文件描述符打印到不同的目的地。此外,文章还展示了如何在自定义的myshell中实现命令行重定向功能,包括处理输出、追加和输入重定向的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

了解重定向之前需要明白文件描述符的工作规则,可以看这篇文章:文件系统

最关键的一点是:在进程中,在文件描述符表中,会将最小的、没有被使用的数组元素分配给新文件。

重定向的原理

输出重定向

以下是测试代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define LOG "log.txt"

int main()
{
    close(1);//关闭标准输出
    int fd = open(LOG, O_CREAT | O_WRONLY | O_TRUNC, 0666);

    printf("hello world!\n");
    printf("hello world!\n");
    printf("hello world!\n");
    printf("hello world!\n");
    printf("hello world!\n");

    return 0;
}

如果我们把close(1);//关闭标准输出这句代码先注释掉,运行结果如下——打印到显示屏上。1号文件描述符对应的就是stdout文件。
在这里插入图片描述
那如果我们把close(1);//关闭标准输出这句代码加上,运行结果如下——可以发现,运行后发现没有在命令行打印,并且在生成的log.txt文件中发现了5行hello world!
在这里插入图片描述
解析:
首先因为0,1,2号文件描述符分别对应标准输入输出和错误,我们关闭1号描述符即关闭了stdout;又因为文件描述符的分配规则:在文件描述符表中,会将最小的、没有被使用的数组元素分配给新文件。所以当我们打开新的文件log.txt的时候,会将现在空出来的1号文件描述符分配给log.txt文件;而printf默认向显示器打印,即默认输出到准输出流stdout,但是printf是根据数组下标来打印的,它并不知道对应数组下标的fd实际指向的是谁,此时1号文件描述符指向的是log.txt,所以就会发生打印输出到log.txt文件的现象,实际上着就是输出重定向。

输入重定向

首先我们向log.txt文件中,写入数据:1 2
在这里插入图片描述

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

#define LOG "log.txt"

int main()
{
    close(0);//关闭标准输入
    int fd = open(LOG, O_RDONLY);

    int a,b;
    scanf("%d %d",&a,&b);
    printf("a=%d b=%d\n",a,b);                                                                                                                                                 
    return 0;
} 

运行结果如下:
在这里插入图片描述
解析:
对于输入重定向,同样的方法,以O_RDONLY(只读模式)打开文件,如果关闭0号文件描述符标准输入stdin,打开文件后会将文件地址填入0号下标,此时在进程中如果使用scanf输入,就不会从默认的标准输入stdin——键盘输入,而是直接从打开的文件中读取数据并输入。

追加重定向

对于追加重定向,将输出重定向中打开文件的方式由O_TRUNC(对文件内容做清空)改为O_APPEND(追加)即可,所以输出重定向与追加重定向唯一的区别就是文件打开的方式不同。

重定向原理:
在上层无法感知的情况下,在OS操作系统内部,更改进程对应的文件描述符表中,特定下标的指向!!!这个特定下标指的就是0——标准输入,1——标准输出,2——标准错误!!!

在这里插入图片描述


再看一个测试,请看下列代码:

  1 #include<iostream>
  2 #include<cstdio>                                                                                                                                                                 
  3 
  4 int main()
  5 {
  6     //Linux下一切皆文件
  7     //C
  8     printf("hello printf->stdout\n");
  9     fprintf(stdout,"hello fprintf->stdout\n");
 10     fprintf(stderr,"hello fprintf->stderr\n");
 11 
 12     //C++
 13     std::cout << "hello cout -> cout" << std::endl;
 14     std::cerr << "hello cerr -> cerr" << std::endl;
 15 }

运行结果:标准输出和标准错误都会向显示器打印。

在这里插入图片描述
而输出重定向后,只有fprintf指定文件流stdout的与cout的打印到了文件log.txt中,而stderr与cerr还依旧向显示器文件打印。重定向时只会对标准的输出进行正常的重定向,但是标准错误不受重定向的影响。
在这里插入图片描述
解析:

  1. stdout,cout——>1,他们都是向1号文件描述符对应的文件进行打印。而输出重定向,只改变1号对应的指向,重定向后1号指向了log.txt文件,所以printf,fprintf,cout就往log.txt文件中打印了。
  2. stderr,cerr——>2,他们都是向2号文件描述符对应的文件进行打印。所以不影响2号。

经过以上学习,那么如何把常规消息,打印到log_normal,异常消息打印到log_error呢?

测试代码:

  1 #include <unistd.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 #include <stdio.h>
  6 
  7 #define LOG_NORMAL "logNormal.txt"
  8 #define LOG_ERROR "logError.txt"
  9 
 10 int main()
 11 {
 12     printf("hello printf->stdout\n");
 13     printf("hello printf->stdout\n");
 14     printf("hello printf->stdout\n");
 15     printf("hello printf->stdout\n");
 16     printf("hello printf->stdout\n");
 17 
 18     fprintf(stdout,"hello fprintf->stdout\n");
 19     fprintf(stdout,"hello fprintf->stdout\n");
 20     fprintf(stdout,"hello fprintf->stdout\n");
 21     fprintf(stderr,"hello fprintf->stdout\n");
 22     fprintf(stdout,"hello fprintf->stdout\n");
 23     fprintf(stdout,"hello fprintf->stdout\n");
 24 
 25     return 0;                                                                                                                                                                    
 26 }

运行结果:所以打印信息都粘在一块了
在这里插入图片描述
正确写法:

  1 #include <unistd.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 #include <stdio.h>
  6 
  7 #define LOG_NORMAL "logNormal.txt"
  8 #define LOG_ERROR "logError.txt"
  9 
 10 int main()
 11 {
 12     close(1);//关闭标准输出-stdout
 13     open(LOG_NORMAL, O_WRONLY | O_CREAT | O_APPEND, 0666);//这个时候1号文件描述符对应的是logNormal.txt文件
 14     //所以原来应该输出到stdout显示器的文件,会被输到logNormal.txt文件中
 15 
 16     close(2);//关闭标准错误-stderr 
 17     open(LOG_ERROR, O_WRONLY | O_CREAT | O_APPEND, 0666);                                                                                                                        
 18 
 19     printf("hello printf->stdout\n");
 20     printf("hello printf->stdout\n");
 21     printf("hello printf->stdout\n");
 22     printf("hello printf->stdout\n");
 23     printf("hello printf->stdout\n");
 24 
 25     fprintf(stdout,"hello fprintf->stdout\n");
 26     fprintf(stdout,"hello fprintf->stdout\n");
 27     fprintf(stdout,"hello fprintf->stdout\n");
 28     fprintf(stderr,"hello fprintf->stderr\n");
 29     fprintf(stdout,"hello fprintf->stdout\n");
 30     fprintf(stdout,"hello fprintf->stdout\n");
 31 
 32     return 0;
 33 }

运行结果:
在这里插入图片描述
在这里插入图片描述

也可以在命令行输入以下代码,进行重定向:

./myproc 1>log.txt 2>err.txt

上述代码,我们用close函数,分别将1,2号关闭,log_normal,txt文件对应的文件描述符就变成了1,log_error文件的描述符对应的就是2,而stdout和stderr默认指向1,2号文件描述符,所以以后文件量大的话,就可以使用这种方法将错误消息都筛选到一个文件中,便于观察。

dup函数

前面讲原理时所用的方法:关闭相关的文件描述符指向的文件,然后再打开文件来实现重定向。这种方法平时不会使用,只是讲原理时使用。下面介绍重定向的实际使用方法:利用dup函数进行重定向

dup2如果调用成功,返回newfd,否则返回-1。
在这里插入图片描述

> log.txt //一个大于符号+文件名即可清空这个文件

dup函数的使用方式:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/stat.h>
  7 #include<fcntl.h>
  8 
  9 #define LOG_NORMAL "logNormal.txt"
 10 #define LOG_ERROR "logError.txt"
 11 
 12 int main()
 13 {
 14     int fd = open(LOG_NORMAL, O_CREAT | O_WRONLY | O_APPEND, 0666);
 15     if(fd<0)
 16     {
 17         perror("open error");
 18         return 1;
 19     }
 20 
 21     dup2(fd,1); ////1号文件描述符重定向到fd1                                                                                                                                                                
 22 
 23     printf("hello world!\n");
 24 
 25     close(fd);//关闭fd
 26 }

运行结果:
在这里插入图片描述

添加重定向功能到myshell

这里的myshell指的是我们自己实现的简易命令行解释器,想学习之前实现的myshell可以看这篇文章:进程控制——简易shell

思路如下:

  1. 判断获取到的命令,遍历命令字符串,看看是否有>输出重定向,>>追加重定向,<输入重定向。
  2. 设置flag变量,用于标记重定向的类型,flag为0表示命令当中包含输出重定向,flag为1表示命令中包含追加重定向,flag为2表示输入重定向。
  3. 找到重定向之后,用flag标记它是哪种重定向之后,将这个位置置成’\0’,跳过一个空格后,后面跟的就是目标文件名,若flag为0,则以写的方式打开目标文件;若type值为1,则以追加的方式打开目标文件,若flag为2,则以读的方式打开目标文件。
  4. 若flag为0或1,则使用dup2函数实现目标文件与标准输出流的重定向;若flag为2,则使用dup2函数实现目标文件与标准输入流的重定向。

代码实现如下:

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后的最大个数
int main()
{
	int flag = 0; //0 >, 1 >>, 2 <
	char cmd[LEN]; //存储命令
	char* myargv[NUM]; //存储命令拆分后的结果
	char hostname[32]; //主机名
	char pwd[128]; //当前目录
	while (1){
		//获取命令提示信息
		struct passwd* pass = getpwuid(getuid());
		gethostname(hostname, sizeof(hostname)-1);
		getcwd(pwd, sizeof(pwd)-1);
		int len = strlen(pwd);
		char* p = pwd + len - 1;
		while (*p != '/'){
			p--;
		}
		p++;
		//打印命令提示信息
		printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);
		//读取命令
		fgets(cmd, LEN, stdin);
		cmd[strlen(cmd) - 1] = '\0';

		//实现重定向功能
		char* start = cmd;
		while (*start != '\0'){
			if (*start == '>'){
				flag = 0; //遇到一个'>',输出重定向
				*start = '\0';
				start++;
				if (*start == '>'){
					flag = 1; //遇到第二个'>',追加重定向
					start++;
				}
				break;
			}
			if (*start == '<'){
				flag = 2; //遇到'<',输入重定向
				*start = '\0';
				start++;
				break;
			}
			start++;
		}
		if (*start != '\0'){ //start位置不为'\0',说明命令包含重定向内容
			while (isspace(*start)) //跳过重定向符号后面的空格
				start++;
		}
		else{
			start = NULL; //start设置为NULL,标识命令当中不含重定向内容
		}

		//拆分命令
		myargv[0] = strtok(cmd, " ");
		int i = 1;
		while (myargv[i] = strtok(NULL, " ")){
			i++;
		}
		pid_t id = fork(); //创建子进程执行命令
		if (id == 0){
			//child
			if (start != NULL){
				if (flag == 0){ //输出重定向
					int fd = open(start, O_WRONLY | O_CREAT | O_TRUNC, 0664); //以写的方式打开文件(清空原文件内容)
					if (fd < 0){
						error("open");
						exit(2);
					}
					close(1);
					dup2(fd, 1); //重定向
				}
				else if (flag == 1){ //追加重定向
					int fd = open(start, O_WRONLY | O_APPEND | O_CREAT, 0664); //以追加的方式打开文件
					if (fd < 0){
						perror("open");
						exit(2);
					}
					close(1);
					dup2(fd, 1); //重定向
				}
				else{ //输入重定向
					int fd = open(start, O_RDONLY); //以读的方式打开文件
					if (fd < 0){
						perror("open");
						exit(2);
					}
					close(0);
					dup2(fd, 0); //重定向
				}
			}

			execvp(myargv[0], myargv); //child进行程序替换
			exit(1); //替换失败的退出码设置为1
		}
		//shell
		int status = 0;
		pid_t ret = waitpid(id, &status, 0); //shell等待child退出
		if (ret > 0){
			printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出码
		}
	}
	return 0;
}

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_麦子熟了

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

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

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

打赏作者

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

抵扣说明:

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

余额充值