重定向
1. 什么是重定向?
重定向本质上就是操作文件描述符 (每一个打开的文件或设备在内核中都有一个编号,称为文件描述符),即 修改标准输入/输出/错误 这三个文件描述符(file descriptor,简称 FD)的指向,让它们 不再指向默认终端(屏幕、键盘) ,而是指向 文件、设备或者其他地方。
文件描述符编号 | 描述 | 默认指向 |
---|---|---|
0 | 标准输入 stdin |
键盘 |
1 | 标准输出 stdout |
屏幕 |
2 | 标准错误 stderr |
屏幕 |

比如常见的 shell
命令:
bash
ls > output.txt
背后的本质动作是:
- 关闭文件描述符 1(stdout)
- 打开或创建
output.txt
- 将描述符 1 指向
output.txt
文件
这样,所有本应该显示在屏幕上的内容,都会写入 output.txt
文件。
2. 常见的重定向符号
符号 | 含义 | 功能描述 | 示例 | 效果 |
---|---|---|---|---|
> |
输出重定向(覆盖),stdout |
把标准输出写入到指定文件,若文件存在则 清空 后写入,否则创建 | ls > out.txt |
将 ls 的 stdout 写入(或覆盖)out.txt |
>> |
输出追加,stdout |
把标准输出追加到指定文件末尾,若文件不存在则创建 | echo hi >> out.txt |
将 hi 追加到 out.txt 末尾 |
< |
输入重定向,stdin |
从指定文件读取内容作为标准输入 | wc -l < in.txt |
将 in.txt 作为 stdin 传给 wc -l |
<< |
Here-Document(多行输入) | 在脚本中内联一段文本作为标准输入,直到遇到结束标识符 |
3. 使用 dup2 系统调用
1. dup2 是什么?
dup2
是一个系统调用,用于复制文件描述符。本质作用就是:让两个文件描述符指向同一个内核打开文件表项。经常用于做输入输出重定向。
2. 函数原型
c
#include <unistd.h>
int dup2(int oldfd, int newfd);
参数:
oldfd
: 已经打开的文件描述符(比如文件、管道等)newfd
: 要复制到的新文件描述符(比如 0/1/2)
返回值:成功: 返回 newfd
;失败: 返回 -1
,并设置 errno 错误号(需要 perror 打印)
3. dup2 做了什么事情?(内部流程)
假设调用 dup2(oldfd, newfd)
:
- 如果
oldfd == newfd
:直接返回newfd
,什么也不做(高效优化); - 如果
newfd
已经打开,会 先关闭 newfd(防止资源泄漏); - 然后 让 newfd 指向 oldfd 指向的内核文件表项。
注意:
cpp
┌──────────────┐
oldfd → │ 内核打开文件表 │ ← newfd
└──────────────┘
不是简单复制数字,而是让它们"指向同一块内核资源"!dup2 后,oldfd
和 newfd
同时指向同一个内核文件表结构。修改一边,另一边也受影响(因为指的是同一份数据)。
4. 代码实验验证
如何 将 printf 打印内容重定向到一个文件中:
c
#include <stdio.h> // printf, perror
#include <unistd.h> // dup2, close
#include <fcntl.h> // open
int main()
{
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); // 打开一个文件,准备写入(如果不存在就会自动创建)
if (fd < 0)
{
perror("open failed");
return 1;
}
close(1); // 关闭标准输出 1(stdout)
dup2(fd, 1); // 将打开的文件描述符复制到 1(标准输出)
printf("这行文字被写入到 output.txt 文件中啦!\n"); // 现在所有的 printf 都不会打印到屏幕,而是写到 output.txt
dprintf(fd, "这是直接用 fd 写的一行文字!\n"); // 可以继续用 fd 写数据(可选)
close(fd); // 关闭文件
return 0;
}
运行示例:
bash
gcc dup2_1.c -o dup2_1
./dup2_1
cat output.txt

你会看到 output.txt
文件里出现了程序输出的内容,而不是显示在屏幕上!
5. 标准错误也能重定向
如果想把错误信息也重定向,可以:
c
dup2(fd, STDERR_FILENO); // 把标准错误(2)也重定向到 fd
这样 perror
、fprintf(stderr, ...)
的内容也会写进文件!
6. minishell
重定向
cpp
#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 type = 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 == '>')
{
type = 0; // 遇到一个'>',输出重定向
*start = '\0';
start++;
if (*start == '>')
{
type = 1; // 遇到第二个'>',追加重定向
start++;
}
break;
}
if (*start == '<')
{
type = 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)
{
if (start != NULL) // 重定向符号后面有内容
{
if (type == 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 (type == 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;
}
4. 1(stdout) VS 2(stderr)
还是那句话,在类 Linux
中,每个进程启动时都会默认打开三个 文件描述符(file descriptor),分别对应三个"数据通道(stream)":
描述符 | 名称(英文) | 默认指向 | 常用符号 |
---|---|---|---|
0 | 标准输入(stdin) | 键盘(或重定向的输入文件) | < |
1 | 标准输出(stdout) | 终端屏幕(或重定向的输出文件) | > |
2 | 标准错误(stderr) | 终端屏幕(或重定向的错误文件) | 2> |
1. 基本重定向:分开输出
1>file1.txt
将 stdout 写入file1.txt
2>file2.txt
将 stderr 写入file2.txt
bash
./mytest 1>file1.txt 2>file2.txt # 执行名为 mytest 的程序,并进行输出重定向
# 任何正常输出(如 printf、cout 等)都会进入 file1.txt
# 任何错误信息(如 error: ...、segfault 报错)都会进入 file2.txt
如果只写 >file1.txt
,Shell
会把它当作 1>file1.txt
处理,因为不加数字时默认重定向文件描述符 1(stdout)。
2. 将 stderr 重定向到 stdout 已指向的文件
有时希望把 stderr 合并到与 stdout 相同的目标中,例如:
bash
./mytest >all.txt 2>&1
这里的执行顺序和含义要注意:
>all.txt
: 先把 stdout (1) 重定向到all.txt
。2>&1
: 再把 stderr (2) "重定向到(&)" 描述符 1 当前所指向的地方。
最终,all.txt
会包含正常输出和错误输出,顺序按照程序真正写入时的先后混合在一起。
注意顺序
- 如果写成
./mytest 2>&1 >all.txt
,则会先将 stderr 重定向到 原先 的 stdout(通常还是终端),然后再把 stdout 重定向到all.txt
,结果是:
- stderr 仍然打印到终端
- stdout 写入
all.txt
3. 追加模式(append)
>>
:向文件末尾追加(append),不覆盖原文件2>>
:同理,对 stderr 追加
bash
./mytest >>out.log 2>>err.log
./mytest >>all.log 2>&1 # 追加模式下合并所有输出
4. Bash 的简写特性
Bash 提供了更简洁的写法,将 stdout 与 stderr 同时重定向:
&>
:等价于>file 2>&1
&>>
:等价于>>file 2>&1
bash
./mytest &>both.log # 将 stdout 和 stderr 一次性写入 both.log(覆盖)
./mytest &>>both.log # 将 stdout 和 stderr 追加到 both.log
5. 形象比喻
可把 stdout (1)比作"普通信件通道",stderr(2)比作"特别邮件通道":
- 各走各的信道
1>normal.txt 2>error.txt
- 相当于把普通信件放到 A 信箱,特别邮件放到 B 信箱。
- 合并邮件
>all.txt 2>&1
- 先把普通信件都放进 C 信箱,再把特别邮件也投入到 C 信箱。
5. 总结
重定向 = 文件描述符指向改变 。通过 close + dup2
等系统调用,把标准输入输出 重新绑定 到文件或设备上。
- 单独重定向
cmd 1>out.txt 2>err.txt
- 合并到同一文件
- 覆盖:
cmd >all.txt 2>&1
或cmd &>all.txt
- 追加:
cmd >>all.txt 2>&1
或cmd &>>all.txt
- 覆盖:
- 顺序关键
2>&1
始终要写在将 stdout 重定向之后,才能"接上"正确的目标。