在Linux中,重定向(Redirection)是一种强大的功能,允许用户控制命令的输入来源(
stdin
)和输出目标(stdout
和stderr
)。通过重定向,你可以将命令的输出保存到文件、从文件读取输入,甚至将错误信息与正常输出分离处理。
目录
一、重定向的原理
1、输出重定向原理
2、追加重定向原理
3、输入重定向原理
4、标准输出流和标准错误流虽然都显示在屏幕上,但二者有什么区别呢?
1. 标准输出 (stdout) 和标准错误输出 (stderr)
2. perror("perror")
函数原型
参数的作用
二、系统调用 dup2
功能说明
返回值
使用注意事项
应用示例
一、重定向的原理
理解了文件描述符的概念及其分配规则后,我们就能掌握重定向的原理。通过以下三个示例,你会发现重定向的本质就是改变文件描述符所指向的struct file*对象。
1、输出重定向原理
所谓输出重定向,就是将程序原本要输出到某个文件的数据,转而输出到另一个指定文件中。
例如,若需将原本输出到显示器(文件描述符1)的数据重定向至log.txt文件,可在打开log.txt前先关闭文件描述符1。这样后续打开log.txt时,系统会自动为其分配文件描述符1,实现输出重定向:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(1);int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");fflush(stdout);close(fd);return 0;
}
运行结果后,我们发现显示器上并没有输出数据,对应数据输出到了log.txt文件当中:
补充说明:
printf
函数默认将数据输出到标准输出(stdout)。stdout本质上是一个指向struct FILE
结构体的指针(对应下面红色方框部分),该结构体包含一个文件描述符成员变量。
对于stdout而言,这个文件描述符的值固定为1。因此,printf
实际上是将数据输出到文件描述符为1的设备。
需要注意的是,C语言的数据并不会立即写入操作系统内存,而是先存储在C语言维护的缓冲区中。因此,在使用printf
打印数据后,通常需要调用fflush
函数来强制刷新缓冲区,确保数据被写入目标文件。
2、追加重定向原理
追加重定向和输出重定向的唯一区别就是,输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据。
比如,我们希望将原本输出到"显示器文件"的数据追加写入log.txt文件,可以先关闭文件描述符1,再以追加写入模式打开log.txt。这样就能实现将数据追加到log.txt的重定向操作:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(1);int fd = open("log.txt", O_WRONLY|O_APPEND|O_CREAT, 0666);if(fd < 0){perror("open");return 1;}printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");fflush(stdout);close(fd);return 0;
}
运行结果后,我们发现对应数据便追加式输出到了log.txt文件当中:
3、输入重定向原理
输入重定向就是,将我们本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。
例如,若要让原本从"键盘文件"读取数据的scanf函数改为从log.txt文件读取,可以在打开log.txt前先关闭文件描述符为0的文件(即"键盘文件")。这样后续打开log.txt时,系统会自动为其分配文件描述符0。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(0);int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}char str[40];while (scanf("%s", str) != EOF){printf("%s\n", str);}close(fd);return 0;
}
代码解析
-
关闭标准输入:
close(0)
关闭了文件描述符0(标准输入)。 -
打开文件:
open("log.txt", O_RDONLY | O_CREAT, 0666)
尝试以只读模式打开文件log.txt
,如果文件不存在则创建。由于文件描述符0已被关闭,新打开的文件会占用最小的可用文件描述符,即0。 -
读取输入:
scanf("%s", str)
从文件描述符0(现在是log.txt
)读取数据,直到遇到文件结束符(EOF)。 -
输出内容:
printf("%s\n", str)
将读取到的内容打印到标准输出。
scanf
按空格分隔读取:
-
第一次读取
"hello"
,打印hello
。 -
第二次读取
"world"
,打印world
。 -
第三次读取
"hello"
,打印hello
。 -
第四次读取
"world"
,打印world
。 -
...
-
直到遇到
"hello Linux"
时,scanf
会先读"hello"
,再读"Linux"
。
运行结果后,我们发现scanf函数将log.txt文件当中的数据都读取出来了:
说明一下:
scanf函数是默认从stdin读取数据的,而stdin指向的FILE结构体中存储的文件描述符是0,因此scanf实际上就是向文件描述符为0的文件读取数据。
4、标准输出流和标准错误流虽然都显示在屏幕上,但二者有什么区别呢?
请看以下示例代码,它分别向标准输出流和标准错误流打印了两行字符串:
#include <stdio.h>
int main()
{printf("hello printf\n"); //stdoutperror("perror"); //stderrfprintf(stdout, "stdout:hello fprintf\n"); //stdoutfprintf(stderr, "stderr:hello fprintf\n"); //stderrreturn 0;
}
1. 标准输出 (stdout
) 和标准错误输出 (stderr
)
-
stdout
(标准输出):默认输出到终端(屏幕),通常用于正常程序输出。 -
stderr
(标准错误输出):默认也输出到终端(屏幕),但专用于错误或警告信息,不受重定向影响。
2.
perror("perror")
函数原型
void perror(const char *s);
-
参数
s
:用户提供的字符串,会作为错误消息的前缀。 -
功能:打印当前
errno
对应的系统错误描述,格式为s: 错误描述
。 -
作用:向
stderr
输出错误信息。 -
特点:
-
perror
用于打印最近的系统错误信息(通过errno
获取)。 -
如果之前没有发生错误,可能输出
"perror: Success"
。 -
始终输出到
stderr
。
-
参数的作用
-
如果
s
非空,perror
会先输出s
,后跟冒号和空格,再输出系统错误信息。 -
如果
s
是空字符串 (""
) 或NULL
,则只输出系统错误信息(无前缀)。
直接运行程序,结果很显然就是在显示器上输出四行字符串:
从表面上看,标准输出流和标准错误流似乎没有区别,都会将数据显示在屏幕上。但当我们尝试将程序运行结果重定向到log.txt文件时,就会发现不同之处:只有标准输出的两行内容被写入文件,而标准错误的两行信息仍然显示在屏幕上:
重点:
实际上,重定向操作针对的是文件描述符1(标准输出流),并不会影响文件描述符2(标准错误流)!!!!!!
二、系统调用 dup2
实现重定向只需复制fd_array数组中的元素。比如,当我们将fd_array[3]的内容复制到fd_array[1]时,由于C语言中stdout对应文件描述符1,这样输出就被重定向到了log.txt文件。
在Linux操作系统中提供了系统接口dup2,我们日常开发常常使用dup2系统调用来复制文件描述符,完成重定向。dup2的函数原型如下:
int dup2(int oldfd, int newfd);
功能说明
dup2函数将fd_array[oldfd]的内容复制到fd_array[newfd]中。若newfd文件描述符已打开,函数会先将其关闭。
返回值
成功时返回newfd,失败时返回-1。
使用注意事项
- 当oldfd为无效文件描述符时,调用失败且不会关闭newfd对应的文件
- 若oldfd有效且newfd等于oldfd,函数直接返回newfd而不执行任何操作
应用示例
将log.txt的文件描述符fd和标准输出描述符1传入dup2后,fd_array[fd]内容会复制到fd_array[1]。由于标准输出(stdout)对应文件描述符1,原本输出到显示器的内容将被重定向至log.txt文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}close(1);dup2(fd, 1);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}
代码运行后,我们即可发现数据被输出到了log.txt文件当中: